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;
}
}
I have a listView and setting multiChoiceModeListener on it. It works fine. Now to play an audio item inside the listView I have written
ViewHolder.AuidoXmlLayoutItem.setOnClickListener({...playAudioCode...});
inside the getView of listView adapter class.
because of this now the multiChoiceModeSelection dosent not show selection of listItem when I longPress on AuidoXmlLayoutItem and hence does not show ContextualActionBar.
How can I keep the onClick of the audio item layout and still allow ContextualActionBar to appear on long click of audio item layout
Try to use OnItemClickListener rather than OnClickListener. Follow this way,
view.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// playAudioCode
// change the checkbox state
ViewToChecked checkedTextView = ((ViewToChecked)view);
checkedTextView.setChecked(!checkedTextView.isChecked());
}
});
You might get a concept.
I want to create a listview from which we can use onclick and in which on long press a Context menu comes out. the code is
public class MainActivity extends ListActivity{
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String quizlist[]={"Normal","MCQ 2 Options","MCQ 3 options","MCQ 4 Options"};
ArrayAdapter<String> ab=new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,quizlist);
setListAdapter(ab);
}
}
Thanks in advance
You can register an AdapterView.OnItemLongClickListener on the ListView. So what you need to do is find the list view:
in onCreate try
((ListView) getView).setOnItemLongKlickListener(...)
or
((ListView) findViewById(<the id of your list view>).setOnItemLongKlickListener(...)
When implementing an OnItemLongClickListener you will have to override the onItemLongClick method:
From the documentation:
public abstract boolean onItemLongClick (AdapterView<?> parent, View view, int position, long id)
Added in API level 1 Callback method to be invoked when an item in
this view has been clicked and held. Implementers can call
getItemAtPosition(position) if they need to access the data associated
with the selected item.
Parameters
parent The AbsListView where the click happened
view The view within the AbsListView that was clicked
position The position of the view in the list
id The row id of the item that was clicked
Returns true if the callback consumed the long click, false otherwise
So, parent is your ListView. view is the view of the *long clicked' list item. position is the position inside the list and thus also the position in your array. For id I am not sure whether the default implementations returns a constant or the position.
I want to click on an image and therefore want to register (e.g.) a ClickHandler. The image I get from a ClientResource. This works so far to set the image into a table cell:
MyResources.INSTANCE.css().ensureInjected();
Image colorImage = new Image( MyResources.INSTANCE.colorImage() );
Element colorImageElement = colorImage.getElement();
colorImage.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
System.out.println( event );
}
} );
TableElement table = Document.get().createTableElement();
TableRowElement headRow = table.insertRow(-1);
headRow.insertCell(-1).appendChild( colorImageElement );
RootPanel.get().getElement().appendChild( table );
How can I add a listener to the icon? I tried ClickHandler and to put the image on a PushButton and get the Element from this PushButton but all don't work.
But mind, if I add the widget (Image is a Widget) to a panel it works!
RootPanel.get().add( colorImage );
But I am not working with widgets here but with the Element. So the handler disappears and that's the point I don't get how to preserve this added handler information.
In the end I would like to build a table with different rows where I can click on the icon I get a popup menu and thereby change the colour of the row.
You should be able to just add a ClickHandler (or a MouseDownHandler if that fits your needs better).
Like this:
colorImage.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
// Do something....
}
});
Don't unwrap your widget and append only the DOM elements. The Widget class allows your code to refer to both elements and events at the same time, and deals with possible memory leaks, as well as grouping your code in logical ways.
This might make sense for other frameworks, but in GWT you almost always want to work with the Widgets directly, adding them together, then appending them to the RootPanel.
If you really want to use a html table to build this up, look at the com.google.gwt.user.client.ui.HTMLTable subclasses, com.google.gwt.user.client.ui.Grid and com.google.gwt.user.client.ui.FlexTable. This probably should never be necessary, unless you are adding multiple items to the table - when trying to specify layouts, use actual layout classes.
did you tried to add image.sinkEvents( Event.ONCLICK | Event.MOUSEEVENTS )?
The image has to be inside a focus widget. I don't know why that is, but somewhere the events don't get propagated right and the DOM events don't fire.
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.