I want to customize my cell table's column header. I have to include support for sorting and filtering. I want to have images for both actions. When the user clicks on the sort image, it will toggle (based on ascending / descending order sort ) and the table sorts based on the icon clicked. I am currenty doing this with some messy HTML manipulation , in the onBrowserEvent of my custom header cell. Could someone tell me how I could use GWT's ToggleButton here?
Thanks.
You can't use GWT Widget in Cell Table.
But, you can use a custom Cell for the header.
public class ButtonHeader extends Header<String> {
private String text;
/**
* Construct a new TextHeader.
*
* #param text the header text as a String
*/
public ButtonHeader(String text) {
super(new ButtonCell());
this.text = text;
setUpdater(new ValueUpdater<String>() {
#Override
public void update(String value) {
//When the button is press
}
});
}
#Override
public String getValue() {
return text;
}
}
You can change this for use an other Cell for your usage.
For a (not-so) complete documentation on custom cells check this link.
You WILL have to override onBrowserEvent(), even if it's clumsy, since you can't use a GWT widget in a cell but you can render it. Yet, it'll lose all it's event handling capabilities.
(from this post) Widgets are never attached to the DOM. They exist to be manipulated in memory and then have their HTML extracted and pushed into the DOM. Events from the Widgets, therefore, are not handled. Cell events ARE handled.
So you could just use widget.getElement.getInnerHTML() to render the widget you want in your header (a toggle button or anything else). Despite having this option at hand, my advice would be to use your own SafeHtmlTemplates instead of using getInnerHTML().
Related
In a GWT web app, I am using a DataGrid to manage elements from a database. I represent a list of elements as rows, the columns being editable fields of their characteristics (id, name, description). I am mostly using the EditTextCell class.
I now want to create a custom cell, for a column that has to represent a list of "tags" that can be attached to every element. From this cell, tags could be added, using a + button (that makes a drop-down menu appear or something), and deleted. Each tag should be a kind of button, or interactive widget (I later want to display pop-up with info, trigger actions, etc).
Actually, it would not be so different from the "tags" bar on the Stack Overflow website...
So I have been looking for a solution:
I thought this would be easy to do. I imagined just putting a FlowPanel in the cell, adding/removing Buttons/Widgets dynamically. But it turns out that in GWT Widgets and Cells and very different objects apparently..
I read making use of the AbstractCell class to create a custom cell allows to do anything, but its working is very low level and obscure to me.
I saw that CompositeCell allows to combine various cell widgets into one cell, but I have not found if it is possible to do it dynamically, or if the widgets are going to be the same for all lines throughout a column. I mostly saw examples about, for instance, how to put two Buttons in every cell of a single column.
What is the easiest way to implement what I need?
EDIT:
So, after some tests, I am going for Andrei's suggestion and going "low-level", creating a custom cell extending AbstractCell<>. I could create an appropriate "render" function, that generates a list of html "button", and also attaches Javascript calls to my Java functions when triggering a Javascript event (onclick, onmouseover, onmouseout...).
It is working pretty well. For instance, by clicking the "+" button at the end a tag list, it calls a MenuBar widget that presents the list of tags that can be added.
But I am struggling to find a way to update the underlying data when adding a tag.
To sum up:
I have a CustomData class that represents the data I want to display in each line of the table. It also contains the list of tags as a Set.
ModelTable (extends DataGrid) is my table.
CustomCell (extends AbstractCell) can renders the list of tags as several buttons on a line.
A click on a "+" button in a cell makes a AddTagMenu popup drop down, from which I can click on the tag to add.
How do I update the content of the cell?
I tried playing around with onBrowserEvent, onEnterKeyDown, bus events... with no success. At best I can indeed add a tag element to the underlying object, but the table is not updated.
It's not possible to meet your requirements without going really "low-level", as you call it.
It's relatively easy to create a cell that would render tags exactly as you want them. Plus icon is also easy, if this is the only action on the cell. However, it is very difficult to make every tag within a cell an interactive widget, because the DataGrid will not let you attach handlers to HTML rendered within a cell. You will need to supply your own IDs to these widgets, and then attach handlers to them in your code. The problem, however, is that when the DataGrid refreshes/re-renders, your handlers will most likely be lost. So you will have to attach them again to every tag in every cell on every change in the DataGrid.
A much simpler approach is to create a composite widget that represents a "row", and then add these "rows" to a FlowPanel. You can easily make it look like a table with CSS, and supply your own widget that looks like a table header. You will need to recreate some of the functionality of the DataGrid, e.g. sorting when clicked on "column" header - if you need this functionality, of course.
As you have already noted, using CompositeCell could be a way to get what you want.
The idea is to create a cell for every tag and then (during rendering) decide which one should be shown (rendered). Finally combine all those cells into one by creating a CompositeCell.
The main disadvantage of this solution is that you need to know all possible tags before you create a DataGrid.
So, if you have a fixed list of possible tags or can get a list of all existing tags and this list is reasonably small, here is a solution.
First, we need to know which tag is represented by a column so I extended a Column class to keep information about a tag. Please, note that TagColumn uses ButtonCell and also handles update when the button is clicked:
public class TagColumn extends Column<DataType, String> {
private TagEnum tag;
public TagColumn(TagEnum tag) {
super(new ButtonCell());
this.tag = tag;
setFieldUpdater(new FieldUpdater<DataType, String>() {
#Override
public void update(int index, DataType object, String value) {
Window.alert("Tag " + getTag().getName() + " clicked");
}
});
}
public TagEnum getTag() {
return tag;
}
#Override
public String getValue(DataType object) {
return tag.getName();
}
}
Then create a cell for each tag (I have hard-coded all tags in a TagEnum):
List<HasCell<DataType, ?>> tagColumns = new ArrayList<HasCell<DataType, ?>>();
for(TagEnum tag : TagEnum.values())
tagColumns.add(new TagColumn(tag));
Now, the most important part: decide either to show the tag or not - overwrite render method of the CompositeCell:
CompositeCell<DataType> tagsCell = new CompositeCell<DataType>(tagColumns) {
#Override
protected <X> void render(Context context, DataType value, SafeHtmlBuilder sb, HasCell<DataType, X> hasCell) {
if(value.getTagList().contains(((TagColumn) hasCell).getTag()))
super.render(context, value, sb, hasCell);
else
sb.appendHtmlConstant("<span></span>");
}
};
This is important to always render any element (for example empty span when the tag should not be shown). Otherwise the CompositeCell's implemantation will get confused when accessing sibling elements.
Finally, full, working example code:
private DataGrid<DataType> getGrid() {
DataGrid<DataType> grid = new DataGrid<DataType>();
List<HasCell<DataType, ?>> tagColumns = new ArrayList<HasCell<DataType, ?>>();
for(TagEnum tag : TagEnum.values())
tagColumns.add(new TagColumn(tag));
CompositeCell<DataType> tagsCell = new CompositeCell<DataType>(tagColumns) {
#Override
protected <X> void render(Context context, DataType value, SafeHtmlBuilder sb, HasCell<DataType, X> hasCell) {
if(value.getTagList().contains(((TagColumn) hasCell).getTag()))
super.render(context, value, sb, hasCell);
else
sb.appendHtmlConstant("<span></span>");
}
};
Column<DataType, DataType> tagsColumn = new Column<DataType, DataType>(tagsCell) {
#Override
public DataType getValue(DataType object) {
return object;
}
};
grid.addColumn(tagsColumn, "Tags");
grid.setRowData(Arrays.asList(
new DataType(Arrays.asList(TagEnum.gwt)),
new DataType(Arrays.asList(TagEnum.table, TagEnum.datagrid)),
new DataType(Arrays.asList(TagEnum.datagrid, TagEnum.widget, TagEnum.customCell)),
new DataType(Arrays.asList(TagEnum.gwt, TagEnum.table, TagEnum.widget, TagEnum.customCell)),
new DataType(Arrays.asList(TagEnum.gwt, TagEnum.customCell)),
new DataType(Arrays.asList(TagEnum.gwt, TagEnum.table, TagEnum.datagrid, TagEnum.widget, TagEnum.customCell))
)
);
return grid;
}
public class TagColumn extends Column<DataType, String> {
private TagEnum tag;
public TagColumn(TagEnum tag) {
super(new ButtonCell());
this.tag = tag;
setFieldUpdater(new FieldUpdater<DataType, String>() {
#Override
public void update(int index, DataType object, String value) {
Window.alert("Tag " + getTag().getName() + " clicked");
}
});
}
public TagEnum getTag() {
return tag;
}
#Override
public String getValue(DataType object) {
return tag.getName();
}
}
public class DataType {
List<TagEnum> tagList;
public DataType(List<TagEnum> tagList) {
this.tagList = tagList;
}
public List<TagEnum> getTagList() {
return tagList;
}
}
public enum TagEnum {
gwt ("gwt"),
table ("table"),
datagrid ("datagrid"),
widget ("widget"),
customCell ("custom-cell");
private String name;
private TagEnum(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Is there any way to SuggestBox to CellTable? Maybe there is another solution then SuggestBox?
I need to get an editable cell with suggestion feature?
I'm using GWT 2.4.
I don't think you can add it directly in. Try using a ClickableTextCell as the cell for that column. Then code your ValueUpdater (which will be called when the cell is clicked) to open up a DialogBox. Put your SuggestBox, and other widgets (OK button, Cancel button, and such), inside that DialogBox. Initialize the SelectionBox with the current contents of the cell. The DialogBox will likely be a DialogBox subclass with extra state data you initialize with the object for that CellTable row as well as the field for that column, so that the OK action knows what field on what object to update with the new contents of the SuggestBox. Essentially it's a popup editor. Not ideal, because users will expect the editor to be embedded in the CellTable, but there are only a few cell editors available (EditTextCell, DatePickerCell, SelectionCell and CheckboxCell, and maybe another variant of text editing), but I've used this technique, and really, it's not too bad.
I ended up using FlexTable instead of CellTable. With FlexTable you may put any widget inside a table cell.
I needed this also and found a solution (under testing, but solong it is working):
I copied the Code from TextInputCell into a new Class SuggestBoxTextInputCell
public class SuggestBoxTextInputCell extends AbstractInputCell<String, SuggestBoxTextInputCell.ViewData> {
MySuggestBox suggestBox;
and added some lines to the onBrowserEvent method:
// Ignore events that don't target the input.
InputElement input = getInputElement(parent);
String eventType = event.getType();
if (BrowserEvents.FOCUS.equals(eventType)) {
TextBox textBox = new MyTextBox(input);
suggestBox = new MySuggestBox(getSuggestOracle(), textBox);
suggestBox.onAttach();
}
Element target = event.getEventTarget().cast();
The classes MySuggestBox and MyTextbox exist only to make the needed constructor and methods public:
private class MyTextBox extends TextBox {
public MyTextBox(Element element) {
super(element);
}
}
private class MySuggestBox extends SuggestBox {
public MySuggestBox(SuggestOracle suggestOracle, TextBox textBox) {
super(suggestOracle, textBox);
}
#Override
public void onAttach() {
super.onAttach();
}
}
getSuggestOracle() only delivers the needed SuggestOracle. Hope someone can use this solution.
I needed this as a solution so I play around with the solution provided by Ande Hofer.
The exact same issue met by Ankit Singla, when the suggestbox is working fine when I press "Enter" key, but not from the "Mouse Click".
I go on further and add-on this onto the solution.
if (BrowserEvents.FOCUS.equals(eventType)) {
...
...
suggestbox.addSelectionHandler(new SelectionHandler<Suggestion>() {
#Override
public void onSelection(SelectionEvent<Suggestion> event) {
Suggestion selectedSuggestion = event.getSelectedItem();
String selectedValue = selectedSuggestion.getReplacementString();
onSuggestSelected(input, selectedValue, valueUpdater);
}
});
suggestbox.onAttach();
}
and a private function
private void onSuggestSelected(Element input, String value,
ValueUpdater<String> valueUpdater) {
input.blur();
suggestbox.onDetach();
if (suggestbox.getSuggestionDisplay().isSuggestionListShowing()) {
((DefaultSuggestionDisplay) suggestbox.getSuggestionDisplay()).hideSuggestions();
}
valueUpdater.update(value);
}
So far so good.
Our requirement is to make an editable grid using the CellTable containing custom widgets in its cell. The custom widget is having text box and search button associated with the text box. To add the custom widget as a cell created a subclass of AbstractEditableCell class (provided by GWT) and had override render() and onBrowserEvent() methods.
The render(Context context, String value, SafeHtmlBuilder sb) method of the custom widget cell creates a Safe html for the widget and render this safe html in to the cell. But the problem i am facing is, the custom widget is rendered correctly but it loses its associates events. The render method in given below :
if (viewData.isEditing()) {
textBoxSelector.setText(text);
OnlyToBeUsedInGeneratedCodeStringBlessedAsSafeHtml safeHtmlObj = new OnlyToBeUsedInGeneratedCodeStringBlessedAsSafeHtml(textBoxSelector.toString());
sb.append(safeHtmlObj);
} else {
// The user pressed enter, but view data still exists.
sb.append(html);
}
If I try to add the widget in the render() method using the following code, it does not add the widget.
int left = parent.getAbsoluteLeft();
int top = parent.getAbsoluteTop();
String elementId = "ID" + left + top;
try {
parent.setId(elementId);
// parent.removeFromParent();
RootPanel.get(elementId).add(textBoxSelector);
} catch (AssertionError error) {
RootPanel.get(elementId).add(textBoxSelector);
}
I'd really appreciate if anyone can guide me in achieving addition of widget in the CellTable without it losing associated events.
GWT's Cells are non-compatible with GWT Widgets. This means that you could not have a GWT widget placed inside of a cell and have it still function. Cells do have an alternative event handling mechanism though (covered below)
The reason for this is that Cells are closer to stateless renderers. Given a data object the Cell will spit out HTML. A single Cell will be reused over and over - spitting out HTML for various elements on the page - and never maintaining references to any of the DOM elements it creates.
In your above example, you call "someWidget.toString()". Doing this will only return the HTML representation of your widget and is what is loses your event handling.
Handling Events in Cells
GWT Dev Guide for Custom Cells (has some additional details)
To handle events in cells, you'll need to override a separate method called onBrowserEvent. You'll also need to configure your cell to be notified of particular events by calling super('click', 'keydown') in your constructor with the list of events you are interested in listening to.
Since Cells are stateless renderers, the onBrowserEvent will be passed a context of the rendered element that was clicked on along with the original data object that your cell rendered. You can then apply changes or manipulate the DOM as needed.
Here is an example taken from the linked dev guide above:
#Override
public void onBrowserEvent(
Context context,
Element parent,
String value,
NativeEvent event,
ValueUpdater<String> valueUpdater) {
// Let AbstractCell handle the keydown event.
super.onBrowserEvent(context, parent, value, event, valueUpdater);
// Handle the click event.
if ("click".equals(event.getType())) {
// Ignore clicks that occur outside of the outermost element.
EventTarget eventTarget = event.getEventTarget();
if (parent.getFirstChildElement().isOrHasChild(Element.as(eventTarget))) {
doAction(value, valueUpdater);
}
}
}
How can I change ButtonCell text that's embedded in celltable column when button pressed.
I've onnly seen setFieldUpdater.
Also is there some easy way to update another CellTable column rather then accessing it directly
Cell widgets are "model-based" (MVP), you have to update the object rendered in the row (the one passed to the FieldUpdater) and then tell the CellTable that the value changed and it should redraw (use setRowData, using the index passed to the FieldUpdater).
Something like:
new FieldUpdater<MyObject, String>() {
#Override
public void update(int index, MyObject object, String value) {
object.setSomeField("foo");
cellTable.setRowData(index, Collections.singletonList(object));
}
}
I'm using GWT 1.6.
I am creating a panel that contains a Button and a Label, which I then add to a FlexTable as one of its cells.
The Button is not receiving any Click events. I see that the table supports determining which Cell is clicked on, but in this case, I want the Mouse events to propagate to the various widgets inside the cell. Any idea on how to do that?
Yeah, I hit that, too - no widgets in the table will receive events. I ended up using code like this:
FixedWidthGrid dataTable = createDataTable();
...
dataTable.addTableListener(new TableListener() {
public void onCellClicked(SourcesTableEvents sender, int row, int cell) {
storyViewer.showStory(table.getRowValue(row));
}
});
You could probably start with something like that, then programmatically send events to your button widget to make the appearance of clicking.
If you know how big your table will be use a Grid instead. All of your widgets will receive there events. I have done this and created my own sortable table.
You have to subclass the Button google Class and add a constructor with two additional arguments (int col, int row).
e.g.
public class RuleButton extends Button {
private int row;
private int col;
public RuleButton(String html, ClickListener listener, int row, int col) {
super(html, listener);
setRow(row);
setCol(col);
}
// getters and setters for row and col attributes.
}
When adding the button, call this constructor and pass row and col indexes to it.