CompositeCell let us customize the content of a table cell's in GWT using Java. We can put almost any other group of widget within the table's cell and layout them as we want. Problem is that if we used the html tags to define the layout of the CompositeCell as yet another table (see CompositeCell anonymous class implementation bellow) we loose the event handling for the components of the cell :(.
Running the following code, when we click in the buttons of the cell will realize the popup in response of the event handling IF WE COMMENT the CompositeCell anonymous implementation.
I've been debugging CompositeCell.onBrowserEvent(Context, Element, C, NativeEvent, ValueUpdater) because i think that the definition of the cell layout using HTML table tags breaks the event chain within GWT widgets hierarchy but haven't been successful so far.
Remark: both commented and uncommented versions of the code realize the same GUI layout. This example just intent to show that we loose event handling when customizing cell's content.
public class ActionCellTest implements EntryPoint {
private static final String SERVER_ERROR = "An error occurred while " + "attempting to contact the server. Please check your network "
+ "connection and try again.";
private final GreetingServiceAsync greetingService = GWT.create(GreetingService.class);
public void onModuleLoad() {
CellTable<Person> table = new CellTable<ActionCellTest.Person>();
final List<HasCell<Person, ?>> cells = new LinkedList<HasCell<Person, ?>>();
cells.add(new HasCellImpl("first name", new ActionCell.Delegate<Person>() {
#Override
public void execute(Person object) {
Window.alert(object.getFirstName());
}
}));
cells.add(new HasCellImpl("last name", new ActionCell.Delegate<ActionCellTest.Person>() {
#Override
public void execute(Person object) {
Window.alert(object.getLastName());
}
}));
CompositeCell<Person> cell = new CompositeCell<Person>(cells) {
#Override
public void render(Context context, Person value, SafeHtmlBuilder sb) {
sb.appendHtmlConstant("<table><tbody><tr>");
for (HasCell<Person, ?> hasCell : cells) {
render(context, value, sb, hasCell);
}
sb.appendHtmlConstant("</tr></tbody></table>");
}
#Override
protected <X> void render(Context context, Person value, SafeHtmlBuilder sb, HasCell<Person, X> hasCell) {
Cell<X> cell = hasCell.getCell();
sb.appendHtmlConstant("<td>");
cell.render(context, hasCell.getValue(value), sb);
sb.appendHtmlConstant("</td>");
}
#Override
protected Element getContainerElement(Element parent) {
return parent.getFirstChildElement().getFirstChildElement().getFirstChildElement();
}
};
table.addColumn(new TextColumn<ActionCellTest.Person>() {
#Override
public String getValue(ActionCellTest.Person object) {
return object.getFirstName() + " " + object.getLastName();
}
}, "name");
table.addColumn(new Column<Person, Person>(cell) {
#Override
public Person getValue(ActionCellTest.Person object) {
return object;
}
}, "composite");
LinkedList<Person> data = new LinkedList<ActionCellTest.Person>();
data.add(new Person("Amy", "Reed"));
data.add(new Person("Tim", "Gardner"));
table.setRowData(data);
RootPanel.get().add(table);
}
private class HasCellImpl implements HasCell<Person, Person> {
private ActionCell<Person> fCell;
public HasCellImpl(String text, Delegate<Person> delegate) {
fCell = new ActionCell<Person>(text, delegate);
}
#Override
public Cell<Person> getCell() {
return fCell;
}
#Override
public FieldUpdater<Person, Person> getFieldUpdater() {
return null;
}
#Override
public Person getValue(Person object) {
return object;
}
}
private class Person {
private String fFirstName;
private String fLastName;
public Person(String first, String last) {
fFirstName = first;
fLastName = last;
}
public String getFirstName() {
return fFirstName;
}
public String getLastName() {
return fLastName;
}
}
}
This is a known issue which will be fixed in the upcoming GWT 2.5 (a matter of weeks): http://code.google.com/p/google-web-toolkit/issues/detail?id=5714
(in the mean time, you can run off trunk or try backporting the change)
Related
In my project, I have a TableViewer which needs to show around 3000 items and also filter them. Without SWT.VIRTUAL the table takes multiple seconds to display.
So, I implemented ILazyContentProvider and not using "ArrayContentProvider" but now I am facing issues in filtering the elements of TableViewer.
I am writing the below code for filtering but the select method is not running at all.
private static class DefaultFilterextends ViewerFilter {
private String searchText;
public void setSearchText(final String searchText) {
this.searchText = searchText;
}
#Override
public boolean select(Viewer viewer, Object parentElement, Object element) {
if (this.searchText == null) {
return true;
}
return (element.toString().contains(searchText) || element.toString().equals(searchText));
}
}
The below ContentProvider, I am writting
private class LazyContentProvider implements ILazyContentProvider {
private TableViewer viewer;
private List<String> elements;
public LazyContentProvider(TableViewer viewer) {
this.viewer = viewer;
}
#Override
public void dispose() {
}
#Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
this.elements = (List<String>) newInput;
}
#Override
public void updateElement(int index) {
viewer.replace(elements.get(index), index);
}
}
Can someone please guide me how can I do the filteration of elements in TableViewer with SWT.VIRTUAL?
TableViewer does not support filtering when SWT.VIRTUAL is used. Instead you must do the filtering in the content provider.
The content provider will need to maintain a filtered list of elements. For example:
public class LazyContentProvider implements ILazyContentProvider
{
private TableViewer viewer;
private List<String> allElements;
private List<String> filteredElements;
public LazyContentProvider()
{
//
}
#Override
public void dispose()
{
//
}
#SuppressWarnings("unchecked")
#Override
public void inputChanged(final Viewer viewer, final Object oldInput, final Object newInput)
{
this.viewer = (TableViewer)viewer;
allElements = (List<String>)newInput;
filteredElements = allElements;
}
#Override
public void updateElement(final int index)
{
viewer.replace(filteredElements.get(index), index);
}
public void filter(final String searchText)
{
filteredElements = allElements.stream().filter(element -> element.contains(searchText)).toList();
viewer.setItemCount(filteredElements.size());
viewer.refresh();
}
}
Here I have added a filteredElements list that contains just the current filtered elements, this is used in updateElement.
I added a filter method which updates the filtered elements list. It also tells the table viewer about the new item count and refreshes the table.
The .toList() method in the filter stream requires Java 16, for older versions of Java use .collect(Collectors.toList()).
My goal is to display every field of an instance of a Class in a tableView. The Class has a field of type enum which has a field of type String.
The enum shall be displayed in a ComboBox as it's String field name.
Of course it also has to be editable.
Now what isn't working:
The String field of enum class is only displayed if the ComboBox is clicked on, otherwise it is the name of the enum constant. Also, if another enum in the combobox is selected, it can't be commited for edit. Clicking return doesn't deselect the Combobox neither is the method commitEdit invoked. If an other column is selected for edit, the attempted edit is cancelled.
I put some effort into trying to figure this out, so I thought maybe one could help me here.
As the original task is about much bigger classes in enterprise software, I abstracted it to ask this question.
I know I could make the column holding the enum of type String and make it work with MyEnum.values() and MyEnum.valueOf() but that could not go into production due to bad performance as the original classes are too big.
Here is my code as an example, if you don't understand the problems just try to use the combobox once and you'll see.
Class which is type of TableView:
public class MyClass {
private MyEnum myEnum;
private String string;
public MyClass(MyEnum myEnum, String string) {
this.myEnum = myEnum;
this.string = string;
}
public MyEnum getMyEnum() {
return myEnum;
}
public void setMyEnum(MyEnum myEnum) {
this.myEnum = myEnum;
}
public String getString() {
return string;
}
}
It's enum field:
public enum MyEnum {
EnumOne("First Enum"),
EnumTwo("Second Enum");
private String name;
public String getName() {
return name;
}
private MyEnum(String name) {
this.name = name;
}
}
FX App:
public class NewFXMain extends Application {
#Override
public void start(Stage primaryStage) {
ObservableList<MyClass> items = FXCollections.observableArrayList();
items.add(new MyClass(MyEnum.EnumOne, "String"));
TableView<MyClass> table = new TableView(items);
table.setEditable(true);
TableColumn<MyClass, MyEnum> myEnumColumn = new TableColumn();
TableColumn<MyClass, String> stringColumn = new TableColumn();
stringColumn.setCellFactory(TextFieldTableCell.forTableColumn());
stringColumn.setCellValueFactory(data -> new ReadOnlyStringWrapper(data.getValue().getString()));
myEnumColumn.setCellFactory((param) -> new MyEnumComboBoxCell());
myEnumColumn.setCellValueFactory(data -> new ReadOnlyObjectWrapper(data.getValue().getMyEnum()));
myEnumColumn.setOnEditCommit(
event -> {
event.getRowValue().setMyEnum(event.getNewValue());
System.out.println(event.getRowValue().getMyEnum());
});
table.getColumns().addAll(myEnumColumn, stringColumn);
StackPane root = new StackPane();
root.getChildren().add(table);
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
class MyEnumComboBoxCell extends ComboBoxTableCell<MyClass, MyEnum> {
private ComboBox<MyEnum> box;
public MyEnumComboBoxCell() {
box = new ComboBox<>(FXCollections.observableArrayList(MyEnum.values()));
box.setCellFactory(new Callback<ListView<MyEnum>, ListCell<MyEnum>>() {
#Override
public ListCell<MyEnum> call(ListView<MyEnum> param) {
return new ListCell<MyEnum>() {
#Override
protected void updateItem(MyEnum item, boolean empty) {
super.updateItem(item, empty);
if ( item != null ) setText(item.getName());
}
};
}
});
}
#Override
public void startEdit() {
super.startEdit();
setGraphic(box);
}
#Override
public void commitEdit(MyEnum newValue) {
super.commitEdit(newValue);
if ( newValue != null ) {
setText(newValue.getName());
getTableView().getSelectionModel().getSelectedItem().setMyEnum(newValue);
box.setValue(newValue);
}
}
#Override
public void updateItem(MyEnum item, boolean empty) {
super.updateItem(item, empty);
if ( empty ) {
setGraphic(null);
} else {
setGraphic(null);
setText(item.getName());
}
}
}
Instead of setting the name in updateItem use a StringConverter like:
public class MyEnumConverter extends StringConverter<MyEnum>{
#Override public String toString(MyEnum enumConstant) {
return enumConstant.getName();
}
#Override public MyEnum fromString(String string) {
return MyEnum.valueOf(string);
}
}
Then in the cell's constructor:
this.setConverter(new MyEnumConverter());
Edit: You may not #Override all of the ComboBoxTableCell's methods, since all of them are working like you want. On the other hand you should not specify an own ComboBox for the table cell, since it has one. You just have to add a StringConverter and set the items.
You may use like this:
myEnumColumn.setCellFactory((param) -> new ComboBoxTableCell<>(new StringConverter<MyEnum>() {
#Override public String toString(MyEnum object) {
return object.getName();
}
#Override public MyEnum fromString(String string) {
return MyEnum.valueOf(string);
}
}, MyEnum.values()));
If you prefer you can create a separate class for the StringConverter like I mentioned earlier, then just simply:
myEnumColumn.setCellFactory(factory -> new ComboBoxTableCell<>(new MyEnumConverter(), MyEnum.values()));
You can also get rid of the myEnumColumn.setOnEditCommit.
Thanks a lot! Actually I've spent a day on this, partially with another person, so this is really appreciated! :)
But:
I have to implement setOnEditCommit or otherwise the myEnum field backing the tableColumn does not change! With this everything works. Without, only what is displayed is changed.
myEnumColumn.setOnEditCommit(
event ->
{
event.getRowValue().setMyEnum(event.getNewValue());
});
My problem is that I have just created a cellTable but it doesn't work because I see this on my broswer
The red border is of the FlowPanel that contains the table that has a black border, on side there is the GWT.log
Now I've tried everything but I don't know why, maybe it doesn't load the table for something reason. However I'm sure that dataProvider works beacause, as tou can see, the log show that data 'Carrello' are load on the column of my table 'carrello'. Here the code of table:
public class ShopTable extends CellTable {
private CellTable<Carrello> carrello;
private Column<Carrello, String> columnTitolo;
private Column<Carrello, String> columnTipoSupporto;
private AsyncDataProviderCarrello dataProvider;
private String COLUMN_NAME_TITOLO="Titolo film";
private String COLUMN_NAME_SUPPORTO="Tipo";
public ShopTable(){
carrello=new CellTable<>();
createTable();
createWithAsyncDataProvider();
GWT.log("Column example: "+carrello.getColumn(0).toString());
}
private void createTable(){
columnTitolo=buildColumnTitolo();
columnTipoSupporto=buildColumnTipoSupporto();
// NEED TO ADD HEADER (and FOOTER MAYBE)
carrello.addColumn(columnTitolo, "Titolo Film");
carrello.addColumn(columnTipoSupporto, "Tipo");
}
private Column<Carrello, String> buildColumnTitolo(){
columnTitolo=new Column<Carrello, String>(new EditTextCell()) {
#Override
public String getValue(Carrello object) {
GWT.log("aggiungo a carrelloTable: "+object);
return object.getTitolo();
}
};
columnTitolo.setDataStoreName(COLUMN_NAME_TITOLO);
return columnTitolo;
}
private Column<Carrello, String> buildColumnTipoSupporto(){
columnTipoSupporto=new Column<Carrello, String>(new EditTextCell()) {
#Override
public String getValue(Carrello object) {
GWT.log("aggiungo a carrelloTable: "+object);
return object.getTipoSupporto().toString();
}
};
columnTipoSupporto.setDataStoreName(COLUMN_NAME_TITOLO);
return columnTipoSupporto;
}
private void createWithAsyncDataProvider(){
dataProvider=new AsyncDataProviderCarrello();
dataProvider.addDataDisplay(carrello);
dataProvider.updateRowCount(10, false);
//.. SORTING METHOD NEED TO ADD
}
}
Here the code of Widget UIBinder that use the ShopTable
public class CarrelloPage extends Composite {
private static CarrelloPageUiBinder uiBinder = GWT.create(CarrelloPageUiBinder.class);
interface CarrelloPageUiBinder extends UiBinder<Widget, CarrelloPage> {
}
interface MyStyle extends CssResource{
String carrelloTable();
}
#UiField MyStyle style;
#UiField FlowPanel spazio_carrello;
/**
* necessario per dimensionare ad hoc per
* il pannello
*/
private ShopTable carrello;
private void resizeWidget(){
setWidth("100%");
setHeight(Window.getClientHeight() + "px");
}
public CarrelloPage() {
carrello=new ShopTable();
initWidget(uiBinder.createAndBindUi(this));
carrello.setStyleName(style.carrelloTable());
spazio_carrello.add(carrello);
resizeWidget();
Window.addResizeHandler(resizeHandler);
}
private ResizeHandler resizeHandler = new ResizeHandler()
{
public void onResize (ResizeEvent event)
{
setWidgetToMaxWidthAndHeight();
}
};
private void setWidgetToMaxWidthAndHeight ()
{
setWidth("100%");
setHeight(Window.getClientHeight() + "px");
}
}
Thanks for attention!
Your CellTable has a height of zero. This is why you don't see it.
You either have to set height of your CellTable in code, or you should add it to a widget that implements ProvidesResize interface, like a LayoutPanel.
First of all - I am a beginner with Java and GWT. I have a scripting language background so please be explicit.
I have a CellTable that is populated with data from a database( ServerKeyWord class gets the data ).
myCellTable.addColumn(new TextColumn<ServerKeyWord>() {
#Override
public String getValue(ServerKeyWord object) {
// TODO Auto-generated method stub
return object.getName();
}
});
The example from above works, but it only shows the data as a text. I need to make it a hyperlink, that when you click it, it opens a new tab to that location.
I've surfed the web and got to the conclusion that I need to override render.
public class HyperTextCell extends AbstractCell<ServerKeyWord> {
interface Template extends SafeHtmlTemplates {
#Template("<a target=\"_blank\" href=\"{0}\">{1}</a>")
SafeHtml hyperText(SafeUri link, String text);
}
private static Template template;
public static final int LINK_INDEX = 0, URL_INDEX = 1;
/**
* Construct a new linkCell.
*/
public HyperTextCell() {
if (template == null) {
template = GWT.create(Template.class);
}
}
#Override
public void render(Context context, ServerKeyWord value, SafeHtmlBuilder sb) {
if (value != null) {
// The template will sanitize the URI.
sb.append(template.hyperText(UriUtils.fromString(value.getName()), value.getName()));
}
}
}
Now ... How do I use the HyperTextCell class with the addColumn method as in the first code example?!
Thank you in advance!
HyperTextCell hyperTextCell = new HyperTextCell();
Column<ServerKeyWord, ServerKeyWord> hyperColumn = new Column<ServerKeyWord, ServerKeyWord>(
hyperTextCell) {
#Override
public ServerKeyWord getValue(ServerKeyWord keyWord) {
return keyWord;
}
};
myCellTable.addColumn(hyperColumn);
In the top of my page I want a dropdown to change the preferred language. If it alters the page is to be reloaded and rendered with the selected langauge.
In my solution the language is not loaded immediately though.
#SuppressWarnings("serial")
public class CurrentLanguageDropdown extends LanguageDropDown {
private String sessionlanguage;
private IModel<String> model;
public CurrentLanguageDropdown(String id, Locale locale) {
super(id, locale);
this.sessionlanguage = locale.getCountry();
GlobalAccountSession session = GlobalAccountSession.get();
model = new SelectedLocaleModel("sessionlanguage", this, new Model<Serializable>(session.getLocale()),
locale.getCountry());
setModel(model);
}
#Override
public void onSelectionChanged(String newSelection) {
GlobalAccountSession session = GlobalAccountSession.get();
model = new SelectedLocaleModel("sessionlanguage", this, new Model<Serializable>(session.getLocale()),
newSelection);
}
#Override
protected boolean wantOnSelectionChangedNotifications() {
return true;
}
String getSessionlanguage() {
return sessionlanguage;
}
private class SelectedLocaleModel extends StringResourceModel {
private String selectedValue = "";
private Component parent;
public SelectedLocaleModel(String resourceKey, Component parent, IModel<?> model, String selectedValue) {
super(resourceKey, parent, model);
this.selectedValue = selectedValue;
this.parent = parent;
}
#Override
public void setObject(java.lang.String object) {
GlobalAccountSession session = GlobalAccountSession.get();
Locale locale = new Locale(object.toString());
session.setLocale(locale);
selectedValue = locale.getLanguage();
}
#Override
public String getObject() {
GlobalAccountSession session = GlobalAccountSession.get();
selectedValue = session.getLocale().getLanguage();
return selectedValue;
}
}
}
Looking at my code I see that getObject is called first, and setObject after that. So rendering seems to be done before the change made by the user is handled.
Can anyone tell me what I'm doing wrong?
You're creating a new model object in onSelectionChanged. This defeats the purpose of a model which should be modified but not replaced. If you need to create a new model object, you'll have to replace the model used by your component too (call setModel(model) again).
I know this isnt a component but an implementation of one but shows the general idea
public class LocaleDropDownPanel extends Panel
{
private Locale sessionLocale;
public LocaleDropDownPanel(String id, Locale locale)
{
super(id);
ArrayList<Locale> choices = Lists.newArrayList(Locale.getAvailableLocales());
//didnt know if you need country or language
IChoiceRenderer<Locale> choiceRenderer = new ChoiceRenderer<Locale>("language");
DropDownChoice<Locale> ddc = new DropDownChoice<Locale>("ddc", new PropertyModel<Locale>(this, "sessionLocale"),
new Model<ArrayList<Locale>>(choices), choiceRenderer)
{
#Override
protected void onSelectionChanged(Locale newSelection)
{
//if you want to save the selected locale in your session.
GlobalAccountSession session = GlobalAccountSession.get();
session.setLocale(newSelection);
}
};
//This Language selected to begin with
ddc.setDefaultModelObject(locale);
}
public Locale getSessionLocale() {
return sessionLocale;
}
public void setSessionLocale(Locale sessionLocale) {
this.sessionLocale = sessionLocale;
}
}