Handling onClick for a checkbox in a CellTable Header - gwt

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

Related

Why "mouseover".equals(event.getType()) is not recognized in GWT Header?

I have a table with a nameColumn.
I want that when user mouseOver the title of nameColumn it will trigger a method
I tried:
Header<String> nameColumnHeader = new Header<String>(new ClickableTextCell()) {
#Override
public String getValue() {
return "Name";
}
#Override
public final void onBrowserEvent(Context context, Element elem, NativeEvent event) {
if ("mouseover".equals(event.getType())) {
//
meaningMessagesPopup.show();
}
else if("mouseout".equals(event.getType())){
meaningMessagesPopup.hide();
}
}
};
table.addColumn(nameColumn, nameColumnHeader);
But seem Gwt did not recognize "mouseover".equals(event.getType())
Do you know how to do the MOUSEOVER event in GWT Header?
i found the answer, that is to create a CustomCell that extends AbstractCell
private class HeaderCell extends AbstractCell<String> {
private String text;
public HeaderCell(String text) {
/*
* Let the parent class know that our cell responds to click events and
* keydown events.
*/
//super("click", "keydown");
super("mouseover");
this.text=text;
}
#Override
public void onBrowserEvent(Context context, Element parent, String value,
NativeEvent event, ValueUpdater<String> valueUpdater) {
// Check that the value is not null.
if (value == null) {
return;
}
// Call the super handler, which handlers the enter key.
super.onBrowserEvent(context, parent, value, event, valueUpdater);
if ("mouseover".equals(event.getType())) {
SafeHtmlBuilder sb=new SafeHtmlBuilder();
sb.appendHtmlConstant("<b>");
sb.appendHtmlConstant("<font color=\"blue\">");
sb.appendEscaped(text);
sb.appendHtmlConstant("</font></b>");
meaningMessagesPopup.setWidget(new HTML(sb.toSafeHtml()));
int left = event.getClientX() -140;
int top = event.getClientY() +30;
meaningMessagesPopup.setPopupPosition(left, top);
// Show the popup
meaningMessagesPopup.show();
}
else if ("mouseout".equals(event.getType())) {
meaningMessagesPopup.hide();
}
}
#Override
public void render(Context context, String value, SafeHtmlBuilder sb) {
/*
* Always do a null check on the value. Cell widgets can pass null to
* cells if the underlying data contains a null, or if the data arrives
* out of order.
*/
if (value == null) {
return;
}
sb.appendEscaped(value);
}
}
Then
Header<String> nameColumnHeader = new Header<String>(new HeaderCell("my Text...")) {
#Override
public String getValue() {
return "Name";
}
};
table.addColumn(nameColumn, nameColumnHeader);

Events of multiple cells in single column

I have two buttons(edit + delete) in one column.
ButtonCell functionButtonCell = new ButtonCell() {
#Override
public void render(final Context context, final SafeHtml data, final SafeHtmlBuilder sb) {
sb.appendHtmlConstant("<button type='button' class='gwt-Button' style = 'width:60px;margin:1px;'>Edit</button>");
sb.appendHtmlConstant("<br/>");
sb.appendHtmlConstant("<button type='button' class='gwt-Button' style = 'width:60px;margin:1px;'>Delete</button>");
}
};
functionColumn = new Column<AdminModel, String>(functionButtonCell) {
public String getValue(final AdminModel object) {
return object.getSeq().toString();
}
};
Bind event for this column in Presenter as
.........
view.getFunctionColumn().setFieldUpdater(new FieldUpdater<AdminModel, String>() {
public void update(final int index, final AdminModel object, final String value) {
Window.alert(index + "-" + value);
}
});
After clicked on edit button , alert-box has appeared , but not on delete button. When I clicked on delete button , nothing has appeared. What would be the problem ?
Addition: How can I decide which button was clicked by user (edit or delete) from my presenter ?
I would really appreciate any of your suggestions because I am troubled on it for a long times. Thanks!
ButtonCell filters events on the first child element only: https://gwt.googlesource.com/gwt/+/2.6.1/user/src/com/google/gwt/cell/client/ButtonCell.java This is why you don't get an event when clicking the second button (note: the goal of that code is to make sure you clicked on the button, and not on blank space around the button; see https://gwt.googlesource.com/gwt/+/a0dc88c8be7408be9554f746eb1ec93798183a28)
The easiest way to implement a two-button cell is to use a CompositeCell; it requires that child cells are rendered into sibling elements though (uses <span>s by default, example below overrides the rendering to use <div>s so your buttons stack each on its own line).
new CompositeCell<AdminModel>(Arrays.asList(
// First button
new HasCell<AdminModel, String>() {
#Override public Cell<String> getCell() { return new ButtonCell(); }
#Override public FieldUpdated<AdminModel, String> getFieldUpdater() {
return new FieldUpdater<AdminModel, String>() {
#Override public void update(int index, AdminModel object, String value) {
Window.alert("Edit " + object.getId());
}
};
}
#Override public String getValue(AdminModel o) {
return "Edit";
}
},
// Second button
new HasCell<AdminModel, String>() {
#Override public Cell<String> getCell() { return new ButtonCell(); }
#Override public FieldUpdated<AdminModel, String> getFieldUpdater() {
return new FieldUpdater<AdminModel, String>() {
#Override public void update(int index, AdminModel object, String value) {
Window.alert("Delete " + object.getId());
}
};
}
#Override public String getValue(AdminModel o) {
return "Delete";
}
}) {
#Override protected <X> void render(Cell.Context context, AdminModel value, SafeHtmlBuilder sb, HasCell<String,X> hasCell) {
// use a <div> instead of the default <span>
Cell<X> cell = hasCell.getCell();
sb.appendHtmlConstant("<div>");
cell.render(context, hasCell.getValue(value), sb);
sb.appendHtmlConstant("</div>");
}
};
(note: in your case, because the button's text doesn't depend on the row object, maybe you should rather use an ActionCell; it would better fit "semantically" with what you're doing, but otherwise it's almost the same; with an ActionCell, you'd use HasCell<AdminModel, AdminModel>, ActionCell<AdminModel>, getFieldUpdater would return null, and thegetValueof theHasCellwould just return theAdminModel` argument as-is).
Otherwise, implement your Cell (or AbstractCell) entirely by yourself.
Ideally, a column should have only one type of cell be it ImageCell, ButtonCell etc. Because all this ImageCell and ButtonCell does not provide any in-built events. The events are handled by FieldUpdater itself which does not have differentiators to identify that which ButtonCell is clicked. Ideally on click of that column, the field-updater will be called.
You should rather create your own composite widget which extends HasCell. This composite widget will have two different buttons and those in built methods are called on click of respective button.
public void onModuleLoad() {
CellTable<Person> table = new CellTable<Person>();
List<HasCell<Person, ?>> cells = new LinkedList<HasCell<Person, ?>>();
cells.add(new ActionHasCell("Edit", new Delegate<Person>() {
#Override
public void execute(Person object) {
// EDIT CODE
}
}));
cells.add(new ActionHasCell("Delete", new Delegate<Person>() {
#Override
public void execute(Person object) {
// DELETE CODE
}
}));
CompositeCell<Person> cell = new CompositeCell<Person>(cells);
table.addColumn(new TextColumn<Person>() {
#Override
public String getValue(Person object) {
return object.getName()
}
}, "Name");
// ADD Cells for Age and Address
table.addColumn(new Column<Person, Person>(cell) {
#Override
public Person getValue(Person object) {
return object;
}
}, "Actions");
}
private class ActionHasCell implements HasCell<Person, Person> {
private ActionCell<Person> cell;
public ActionHasCell(String text, Delegate<Person> delegate) {
cell = new ActionCell<Person>(text, delegate);
}
#Override
public Cell<Person> getCell() {
return cell;
}
#Override
public FieldUpdater<Person, Person> getFieldUpdater() {
return null;
}
#Override
public Person getValue(Person object) {
return object;
}
}
Also, see the link below.
[GWT CellTable-Need to have two buttons in last single cell of each row

GWT ImageCell: Change image dynamically in a DataGrid or CellTable

I have DataGrid where one on of the columns contains images. I used this code to generate the column.
Column<Job, String> expandHideColumn = new Column<Job, String>(
imageCell) {
#Override
public String getValue(Job object) {
return null;
}
#Override
public void render(Context context, Job Object, SafeHtmlBuilder sb) {
sb.appendHtmlConstant("<img src='images/expand.jpeg' style='cursor: pointer' />");
}
}
What I want is on clicking the image it has to change. For this I added a click handler on the ImageCell like this
ImageCell imageCell = new ImageCell() {
#Override
public Set<String> getConsumedEvents() {
Set<String> events = new HashSet<String>();
events.add("click");
return events;
}
};
In the onBrowserEvent method I wrote this
#Override
public void onBrowserEvent(Context context, Element element,
Job job, NativeEvent event) {
if (element.getFirstChildElement().isOrHasChild(
Element.as(event.getEventTarget()))) {
if (element.getFirstChildElement().getPropertyString("src")
.matches("(.*)expand.jpeg")) {
element.getFirstChildElement().setPropertyString("src",
"images/collapse.jpeg");
} else {
element.getFirstChildElement().setPropertyString("src",
"images/expand.jpeg");
}
}
}
I don't think this is a good approach to change images on click event. Is there a better solution?
You can use a column value for know the state of the column :
Column<Job, Boolean> expandHideColumn = new Column<Job, Boolean>(new ImageExpandCollapseCell()) {
#Override
public Boolean getValue(Job object) {
return object.isExpand(); //The object know the expand state ?
}
}
expandHideColumn.setValueUpdater(new FieldUpdater<Job, Boolean>() {
void update(int index, Job object, Boolean value) {
object.setExpand(value);
}
});
The ImageExpandCollapseCell look like this :
public class ImageExpandCollapseCell extends AbstractCell<Boolean> {
final String EXPAND = "images/expand.jpeg";
final String COLLAPSE = "images/collapse.jpeg";
interface Template extends SafeHtmlTemplates {
#Template("<div style=\"float:right\"><img src=\"" + url + "\"></div>")
SafeHtml img(String url);
}
private static Template template;
/**
* Construct a new ImageCell.
*/
public ImageCell() {
super("click"); //Replace your getConsumedEvents()
if (template == null) {
template = GWT.create(Template.class);
}
}
#Override
public void render(Context context, Boolean value, SafeHtmlBuilder sb) {
if (value != null) {
sb.append(template.img(UriUtils.fromSafeConstant(value ? EXPAND : COLLAPSE)));
}
}
#Override
public void onBrowserEvent(Context context, Element element,
Boolean value, NativeEvent event, ValueUpdater<Boolean> valueUpdater) {
valueUpdate.update(!value);
}
}
I improve the proposed version of user905374
It's not a good idea to instantiate new value in the render method.
The column render method call the Cell render method, you musn't replace it !
With the FieldUpdater, you can change the state of the image : expand or collapse and update the cell display (it will be rendered again).

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

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.

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