Let's consider we have the following informations :
As you see an article can be stored in many stores, and vice versa : a store can store many articles : that's the class model (UML )
some code :
FXML Part :
#FXML
private TableView<Article> tblArticles;
#FXML
private TableColumn<Article, String> colStore;
#FXML
private TableColumn<Article, Integer> colQuantity;
getters and setter :
colStore.setCellValueFactory(new PropertyValueFactory<>("store"));
colStore.setCellValueFactory(new PropertyValueFactory<>("quantity"));
I recieve the result seen in the first table but I am not able to do what is in the second table .
And what I want exactly should give the following informations :
So my question is it possible to do this in a TableView ?
Here is a sample app. It follows an MVVM style, which is appropriate for this kind of work. The app was built using Java 13 and will not work in earlier Java versions such as Java 8. It's a relatively long answer, but, ah well, sometimes that is what it takes.
The overall approach is not to create a tableview row for each store that an article is stored in. Instead, we just create a row for each article and we have a custom cell renderer which produces a single formatted cell for all the stores and quantities that that item is stored at.
Now, you could do an alternative implementation based upon a custom rowFactory. However, I do not recommend that approach for this particular task, as I believe it would be unnecessarily complicated to implement and maintain, without providing sufficient value.
Another way to do this is to use nested columns. This approach, when appropriate care is taken, does allow you to create a tableview row for each store that an article is stored in. If you do this, you need some way of populating different data depending on whether a row is either the first row in the group or not. You don't allow the user to reorder and sort data in the table, as that would be quite difficult to cater for because the notion of what is the "first row in the group" would be forever changing. To allow for appropriate rendering with nested columns, you end up with a slightly different view model (the FlatLineItem class below and the accompanying method in the LineItemService that retrieves them).
The image below demonstrates the output of a TableView with a custom cell renderer on the left and a TableView using nested columns on the right. Note how the selection works differently in each case. On the left when a row is selected, it includes all the stores that attached to that row. On the right when the nested columns are used, the row selection is only selecting a row for a given store.
Main application class
This sets up a couple of TableViews.
For the first table view, all it does is create a TableView with a column for each of the elements to be displayed. All the data is extracted from a LineItem view model class using a standard PropertyValueFactory. The slightly different thing is a custom cell renderer for a StoredQuantity field via the StoredQuantityTableCell, this is explained later.
The second view uses nested columns and works based upon the FlatLineItem view model class, also using a standard PropertyValueFactory and uses no custom cell renderer.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import java.util.List;
public class AggregateViewApp extends Application {
#Override
public void start(Stage stage) throws Exception {
LineItemService lineItemService = new LineItemService();
TableView<LineItem> tableView = createArticleTableView();
tableView.getItems().setAll(lineItemService.fetchAllLineItems());
TableView<FlatLineItem> nestedTableView = createNestedArticleTableView();
nestedTableView.getItems().setAll(lineItemService.fetchAllFlatLineItems());
HBox layout = new HBox(
40,
tableView,
nestedTableView
);
stage.setScene(new Scene(layout));
stage.show();
}
#SuppressWarnings("unchecked")
private TableView<LineItem> createArticleTableView() {
TableView tableView = new TableView();
TableColumn<LineItem, Long> articleIdCol = new TableColumn<>("Article ID");
articleIdCol.setCellValueFactory(new PropertyValueFactory<>("articleId"));
TableColumn<LineItem, String> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(new PropertyValueFactory<>("articleName"));
TableColumn<LineItem, List<StoredQuantity>> storedArticleCol = new TableColumn<>("Store Quantities");
storedArticleCol.setCellValueFactory(new PropertyValueFactory<>("storedQuantities"));
storedArticleCol.setCellFactory(lineItemStringTableColumn -> new StoredQuantityTableCell());
TableColumn<LineItem, DB.StoredArticle> totalCol = new TableColumn<>("Total");
totalCol.setCellValueFactory(new PropertyValueFactory<>("total"));
tableView.getColumns().addAll(articleIdCol, nameCol, storedArticleCol, totalCol);
tableView.setPrefSize(400, 150);
return tableView;
}
#SuppressWarnings("unchecked")
private TableView<FlatLineItem> createNestedArticleTableView() {
TableView tableView = new TableView();
TableColumn<FlatLineItem, Long> articleIdCol = new TableColumn<>("Article ID");
articleIdCol.setCellValueFactory(new PropertyValueFactory<>("articleId"));
articleIdCol.setSortable(false);
TableColumn<FlatLineItem, String> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(new PropertyValueFactory<>("articleName"));
nameCol.setSortable(false);
TableColumn<FlatLineItem, String> storeCol = new TableColumn<>("Store");
storeCol.setCellValueFactory(new PropertyValueFactory<>("storeName"));
storeCol.setSortable(false);
TableColumn<FlatLineItem, String> storeQuantityCol = new TableColumn<>("Quantity");
storeQuantityCol.setCellValueFactory(new PropertyValueFactory<>("storeQuantity"));
storeQuantityCol.setSortable(false);
TableColumn<FlatLineItem, List<StoredQuantity>> storedArticleCol = new TableColumn<>("Store Quantities");
storedArticleCol.getColumns().setAll(
storeCol,
storeQuantityCol
);
storedArticleCol.setSortable(false);
TableColumn<LineItem, DB.StoredArticle> totalCol = new TableColumn<>("Total");
totalCol.setCellValueFactory(new PropertyValueFactory<>("total"));
totalCol.setSortable(false);
tableView.getColumns().setAll(articleIdCol, nameCol, storedArticleCol, totalCol);
tableView.setPrefSize(400, 200);
return tableView;
}
public static void main(String[] args) {
launch(AggregateViewApp.class);
}
}
StoredQuantityTableCell.java
This takes a list of StoredQuantities which is a tuple of a store name and a quantity of things stored at that store and then renders that list into a single cell, formatting the display internally in a GridView. You could use whatever internal node layout or formatting you wish and add CSS styling to spice things up if necessary.
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.layout.GridPane;
import java.util.List;
class StoredQuantityTableCell extends TableCell<LineItem, List<StoredQuantity>> {
private GridPane storedQuantityPane;
public StoredQuantityTableCell() {
storedQuantityPane = new GridPane();
storedQuantityPane.setHgap(10);
storedQuantityPane.setVgap(5);
}
#Override
protected void updateItem(List<StoredQuantity> storedQuantities, boolean empty) {
super.updateItem(storedQuantities, empty);
if (storedQuantities == null) {
setGraphic(null);
return;
}
storedQuantityPane.getChildren().removeAll(storedQuantityPane.getChildren());
int row = 0;
for (StoredQuantity storedQuantity: storedQuantities) {
storedQuantityPane.addRow(
row,
new Label(storedQuantity.getStoreName()),
new Label("" + storedQuantity.getQuantity())
);
row++;
}
setGraphic(storedQuantityPane);
}
}
LineItem.java
A view model class representing a row in the table.
import java.util.Collections;
import java.util.List;
public class LineItem {
private long articleId;
private String articleName;
private List<StoredQuantity> storedQuantities;
public LineItem(long articleId, String articleName, List<StoredQuantity> storedQuantities) {
this.articleId = articleId;
this.articleName = articleName;
this.storedQuantities = storedQuantities;
}
public long getArticleId() {
return articleId;
}
public String getArticleName() {
return articleName;
}
public List<StoredQuantity> getStoredQuantities() {
return Collections.unmodifiableList(storedQuantities);
}
public int getTotal() {
return storedQuantities.stream()
.mapToInt(StoredQuantity::getQuantity)
.sum();
}
}
StoredQuantity.java
A view model class representing a store name and quantity of things in the store. This is used by the StoredQuantityTableCell to render the stored quantities for a line item.
public class StoredQuantity implements Comparable<StoredQuantity> {
private String storeName;
private int quantity;
StoredQuantity(String storeName, int quantity) {
this.storeName = storeName;
this.quantity = quantity;
}
public String getStoreName() {
return storeName;
}
public int getQuantity() {
return quantity;
}
#Override
public int compareTo(StoredQuantity o) {
return storeName.compareTo(o.storeName);
}
}
FlatLineItem.java
A view model class supporting a table view with nested columns. A flat line item which can be created for each store that an article is stored in.
public class FlatLineItem {
private Long articleId;
private String articleName;
private final String storeName;
private final Integer storeQuantity;
private final Integer total;
private final boolean firstInGroup;
public FlatLineItem(Long articleId, String articleName, String storeName, Integer storeQuantity, Integer total, boolean firstInGroup) {
this.articleId = articleId;
this.articleName = articleName;
this.storeName = storeName;
this.storeQuantity = storeQuantity;
this.total = total;
this.firstInGroup = firstInGroup;
}
public Long getArticleId() {
return articleId;
}
public String getArticleName() {
return articleName;
}
public String getStoreName() {
return storeName;
}
public Integer getStoreQuantity() {
return storeQuantity;
}
public Integer getTotal() {
return total;
}
public boolean isFirstInGroup() {
return firstInGroup;
}
}
LineItemService.java
This translates values from the database into view model objects (LineItems or FlatLineItems) which can be rendered by the views. Note how the getFlatLineItemsForLineItem which constructs the FlatLineItems for the nested column table view has a notion of what it the first row in a group of line items and propagates the the FlatLineItem appropriately based on that, leaving some values null if they are just repeated from the first item in the group, which results in a clean display.
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class LineItemService {
private final DB db = DB.instance();
public List<LineItem> fetchAllLineItems() {
return db.findAllArticles()
.stream()
.map(article -> createLineItemForArticle(article.getArticleId()))
.collect(Collectors.toList());
}
public List<FlatLineItem> fetchAllFlatLineItems() {
return fetchAllLineItems().stream()
.flatMap(lineItem -> getFlatLineItemsForLineItem(lineItem).stream())
.collect(Collectors.toList());
}
private List<FlatLineItem> getFlatLineItemsForLineItem(LineItem lineItem) {
ArrayList<FlatLineItem> flatLineItems = new ArrayList<>();
boolean firstStore = true;
for (StoredQuantity storedQuantity: lineItem.getStoredQuantities()) {
FlatLineItem newFlatLineItem;
if (firstStore) {
newFlatLineItem = new FlatLineItem(
lineItem.getArticleId(),
lineItem.getArticleName(),
storedQuantity.getStoreName(),
storedQuantity.getQuantity(),
lineItem.getTotal(),
true
);
firstStore = false;
} else {
newFlatLineItem = new FlatLineItem(
null,
null,
storedQuantity.getStoreName(),
storedQuantity.getQuantity(),
null,
false
);
}
flatLineItems.add(newFlatLineItem);
}
return flatLineItems;
}
private LineItem createLineItemForArticle(long articleId) {
DB.Article article =
db.findArticleById(
articleId
).orElse(
new DB.Article(articleId, "N/A")
);
List<DB.StoredArticle> storedArticles =
db.findAllStoredArticlesForArticleId(articleId);
return new LineItem(
article.getArticleId(),
article.getName(),
getStoredQuantitesForStoredArticles(storedArticles)
);
}
private List<StoredQuantity> getStoredQuantitesForStoredArticles(List<DB.StoredArticle> storedArticles) {
return storedArticles.stream()
.map(storedArticle ->
new StoredQuantity(
db.findStoreById(storedArticle.getStoreId())
.map(DB.Store::getName)
.orElse("No Store"),
storedArticle.getQuantity()
)
)
.sorted()
.collect(
Collectors.toList()
);
}
}
Mock database class
Just a simple in-memory representation of the database class. In a real app, you would probably use something like SpringData with hibernate to provide the data access repositories using a JPA based object to relational mapping.
The database classes aren't related to the view at all but are just presented here so that a running app can be created within a MVVM style framework.
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
class DB {
private static final DB instance = new DB();
public static DB instance() {
return instance;
}
private List<Article> articles = List.of(
new Article(1, "Hp101"),
new Article(3, "Lenovo303"),
new Article(4, "Asus404")
);
private List<Store> stores = List.of(
new Store(1, "S1"),
new Store(2, "S2")
);
private List<StoredArticle> storedArticles = List.of(
new StoredArticle(1, 1, 30),
new StoredArticle(1, 2, 70),
new StoredArticle(3, 1, 50),
new StoredArticle(4, 2, 70)
);
public Optional<Article> findArticleById(long articleId) {
return articles.stream()
.filter(article -> article.getArticleId() == articleId)
.findFirst();
}
public Optional<Store> findStoreById(long storeId) {
return stores.stream()
.filter(store -> store.getStoreId() == storeId)
.findFirst();
}
public List<StoredArticle> findAllStoredArticlesForArticleId(long articleId) {
return storedArticles.stream()
.filter(storedArticle -> storedArticle.articleId == articleId)
.collect(Collectors.toList());
}
public List<Article> findAllArticles() {
return Collections.unmodifiableList(articles);
}
static class Article {
private long articleId;
private String name;
public Article(long articleId, String name) {
this.articleId = articleId;
this.name = name;
}
public long getArticleId() {
return articleId;
}
public String getName() {
return name;
}
}
static class Store {
private long storeId;
private String name;
public Store(long storeId, String name) {
this.storeId = storeId;
this.name = name;
}
public long getStoreId() {
return storeId;
}
public String getName() {
return name;
}
}
static class StoredArticle {
private long articleId;
private long storeId;
private int quantity;
public StoredArticle(long articleId, long storeId, int quantity) {
this.articleId = articleId;
this.storeId = storeId;
this.quantity = quantity;
}
public long getArticleId() {
return articleId;
}
public long getStoreId() {
return storeId;
}
public int getQuantity() {
return quantity;
}
}
}
Answers to some follow-up questions
Which Approach is the best for updating data ?
All of the approaches I have shown use read only data models and views. To make it read-write would be a bit more work (and out of scope for what I would be prepared to add to this already long answer). Probably, of the two approaches outlined above, the approach which uses a separate row for each store containing an item would be the easiest to adapt to making the data updatable.
Which approach in general I should use to update data ( data are stored for sure in db) ?
Defining a general approach to updating data in a database is out of scope for what I would answer here (it is a purely opinion based answer, as there are many different ways to accomplish this, and as such is off topic for StackOverflow). If it were me, I'd set up a SpringBoot based rest service that connected to the database and have my client app communicate with that. If the app does not need to communicate over the internet, but only communicate with a local DB over a LAN, then adding direct database access by making the app a SpringBoot app and using Spring Data repositories with the embedded H2 database is what I would use.
Is when modifying in a specific row modify in db or wait until user modify in the whole tableview and click on a save button ?
Either way would work, I don't have any strong opinion on one versus the other. I'd probably lean towards the immediate update scenario rather than a delayed save scenario, but it would depend on the app and desired user experience.
Please can you provide me with some code for either to draw a line under every cell or to make it just like usual tableView ( one row gray and one not etc ...)
You can ask that as a separate question. But, in general, use CSS styling. If you use the second approach outlined above which has a row per store, then everything is already a "usual tableView" in terms of styling, with one row gray and one row not, etc., so I don't know that any additional styling is really required in such a case.
here is a good thread about DataProvider.refreshAll() on Vaadin 8.5.1, but it doesn't seem to work this way in Vaadin 11.
I used this starter app to play around. It displays some imaginary product data in a grid.
At first, I added a refresh command to SampleCrudView:
public HorizontalLayout createTopBar() {
...
HorizontalLayout topLayout = new HorizontalLayout();
Button btn = new Button("refresh");
btn.addClickListener(event -> dataProvider.refreshAll());
topLayout.add(btn);
...
return topLayout;
}
The folks from vaadin override getId() in their ProductDataProvider like this to use it as an object identifier:
#Override
public Integer getId(Product product) {
Objects.requireNonNull(product,
"Cannot provide an id for a null product.");
return product.getId();
}
That ProductDataProvider extends ListDataProvider, which is initialized on startup with data from MockDataService, so that we always deal with the same objects. I changed that:
public class MockDataService extends DataService {
...
#Override
public synchronized List<Product> getAllProducts() {
//added ->
MockDataGenerator.resetProductCounter(); //this one sets nextProductId = 1
products = MockDataGenerator.createProducts(categories);
products.stream().forEach(p -> System.out.println(p.getId() + ", " + p.getProductName()));
//<- added
return products;
}
So now you will get new Product instances within the same ID range every time you call getAllProducts():
public class ProductDataProvider extends ListDataProvider<Product> {
...
#Override
public Stream<Product> fetch(Query<Product, SerializablePredicate<Product>> query) {
//added ->
this.getItems().clear();
this.getItems().addAll(DataService.get().getAllProducts());
//<- added
return super.fetch(query);
}
So the point is, this doesn't work - the data in the grid is still the same after "refresh" has been clicked.
Any suggestions?
Regards,
m_OO_m
This is caused by a bug that was fixed a couple a days ago. The fix will be included in the next maintenance release.
I am using the jface tableviewer in an eclipse rcp application to display some values.
Therefore I have written the following snipped ...
tableviewer = new TableViewer(container, SWT.FULL_SELECTION | SWT.BORDER | SWT.SINGLE);
tableviewer.setContentProvider(new ArrayContentProvider());
ColumnViewerToolTipSupport.enableFor(tableviewer, ToolTip.RECREATE);
final Table table = tableviewer.getTable();
table.setHeaderVisible(true);
table.setLinesVisible(true);
TableViewerColumn column = new TableViewerColumn(tableviewer, SWT.NONE);
column.getColumn().setText("col1");
column.getColumn().setResizable(true);
column.setLabelProvider(new ConfigLabelProvider("col1"));
And here here ConfigLabelProvider definition
private class ConfigLabelProvider extends StyledCellLabelProvider {
private String property;
public ConfigLabelProvider(String property) {
this.property = property;
}
#Override
public void update(ViewerCell cell) {
GenericConfigInterfaceEntity config = (GenericConfigInterfaceEntity) cell.getElement();
switch (property) {
case "col1":
cell.setText(AppHelper.preventNull("col1Text col1Text col1Text col1Text col1Text"));
break;
case ...
}
super.update(cell);
}
}
Now my problem is if the column is too small, the default tooltip is displayed trying to show the full cell text value.
BUT I get a tooltip box that is large enough for the whole text but the text isn't shown outside the cell rectange.
If I extend the ConfigLabelProvider from CellLabelProvider the Tooltip is showing up like expected ...
But I need the paint method of the StyledCellLabelProvider.
Any ideas?
Edit 1
I have written a small Java Example Project using SWT and JFACE, because my problems still remain.
My goal is to have an table with a cell-Background without the mousehover (because its looking ugly together) and a custom tooltip.
Here's my TestTable implementation
package main;
import java.util.ArrayList;
import java.util.List;
import model.TestModel;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ColumnViewer;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.StyledCellLabelProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.viewers.ViewerColumn;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
public class TestTable extends Dialog {
private TableViewer tableviewer;
private List<TestModel> entities;
protected TestTable(Shell parentShell) {
super(parentShell);
}
#Override
public void create() {
super.create();
loadData();
}
#Override
protected Control createDialogArea(Composite parent) {
GridData dataLayout;
Composite area = (Composite) super.createDialogArea(parent);
dataLayout = new GridData(GridData.FILL_BOTH);
dataLayout.heightHint = 150;
dataLayout.widthHint = 500;
Composite wrapper = new Composite(area, SWT.NONE);
wrapper.setLayoutData(dataLayout);
wrapper.setLayout(new FillLayout());
tableviewer = new TableViewer(wrapper, SWT.BORDER | SWT.MULTI);
tableviewer.setContentProvider(new ArrayContentProvider());
ColumnViewerToolTipSupport.enableFor(tableviewer);
final Table table = tableviewer.getTable();
table.setLinesVisible(true);
table.setHeaderVisible(true);
createColumns(wrapper);
return area;
}
private void createColumns(Composite wrapper) {
TableViewerColumn firstnameColumn = new TableViewerColumn(tableviewer, SWT.NONE);
firstnameColumn.getColumn().setText("Vorname");
firstnameColumn.setLabelProvider(new StyledCellLabelProvider(StyledCellLabelProvider.COLORS_ON_SELECTION) {
#Override
public void initialize(ColumnViewer viewer, ViewerColumn column) {
super.initialize(viewer, column);
this.setOwnerDrawEnabled(false);
}
#Override
public void update(ViewerCell cell) {
TestModel model = (TestModel) cell.getElement();
cell.setText(model.getFirstname());
cell.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_DARK_GREEN));
}
#Override
public String getToolTipText(Object element) {
TestModel model = (TestModel) element;
return "USE THIS AS TOOLTIP";
}
});
TableViewerColumn lastnameColumn = new TableViewerColumn(tableviewer, SWT.NONE);
lastnameColumn.getColumn().setText("Nachname");
lastnameColumn.setLabelProvider(new StyledCellLabelProvider(StyledCellLabelProvider.COLORS_ON_SELECTION) {
#Override
public void initialize(ColumnViewer viewer, ViewerColumn column) {
super.initialize(viewer, column);
this.setOwnerDrawEnabled(false);
}
#Override
public void update(ViewerCell cell) {
TestModel model = (TestModel) cell.getElement();
cell.setText(model.getLastname());
cell.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_DARK_GREEN));
}
#Override
public String getToolTipText(Object element) {
TestModel model = (TestModel) element;
return "USE THIS AS TOOLTIP";
}
});
for (TableColumn c : tableviewer.getTable().getColumns()) {
c.pack();
}
}
private void loadData() {
entities = new ArrayList<TestModel>();
entities.add(new TestModel("___Firstname1___", "Lastname1", "Username1", "Kommentar"));
entities.add(new TestModel("___Firstname2___", "Lastname2", "Username2", "Kommentar"));
entities.add(new TestModel("___Firstname3___", "Lastname3", "Username3", "Kommentar"));
entities.add(new TestModel("___Firstname4___", "Lastname4", "Username4", "Kommentar"));
entities.add(new TestModel("___Firstname5___", "Lastname5", "Username5", "Kommentar"));
tableviewer.setInput(entities);
tableviewer.refresh();
}
}
And here are some faulty pictures
Here the native TableViewer Tooltip and my custom ToolTip is shown, also the row gets selected (COLORS_ON_SELECTION should prevent that)
Here no tooltip is shown on the second column
And here no tooltip is shown and as you can see the first cell isn't filled up
If I add SWT.FULL_SELECTION the tooltip on column 2 appears but the other issues remain.
I think it's a kind of buggy that Tooltip Support or I am doing it totally wrong.
This solved my problem
https://stackoverflow.com/a/28991593/1822033
The underlaying second tip was shown because the column was too narrow. Setting tableviewer.getTavle().setTooltipText(""); stopped showing the native tip.
Setting it to null displays it anyway!
I am using the TokenAutoComplete library for adding Gmail style chips to my textfield. Everything is working fine. The only problem is that i want to add items to my ChipTextView when the UI is loaded but i cannot find any way to do that. All the items that i add to adapter are displayed as suggestion instead.
My ChipTextView class:
public class ChipTextView extends TokenCompleteTextView {
public ChipTextView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
public ChipTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
public ChipTextView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
#Override
protected Object defaultObject(String text) {
return text;
}
#Override
protected View getViewForObject(Object text) {
String hashtag = (String) text;
LayoutInflater l = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LinearLayout view = (LinearLayout) l.inflate(R.layout.chiptextview_item,
(ViewGroup) ChipTextView.this.getParent(), false);
((TextView) view.findViewById(R.id.tv_text)).setText(hashtag);
return view;
}
}
My fragment From where i am setting up ChipTextView :
ArrayList<String> list=new ArrayList<>();
list.add("hello");
list.add("hi");
list.add("how");
ChipTextView tv_chipview=(ChipTextView ) parentView.findViewById(R.id.tv_chipview);
ArrayAdapter<String> mAdapter;
mAdapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1,list);
tv_chipview.setAdapter(mAdapter);
tv_chipview.allowDuplicates(false);
tv_chipview.setDeletionStyle(TokenDeleteStyle.Clear);
This is how it is shown when i add items to adapter using code
How i want the output to be displayed after setting up. This is how it is displayed when i use my keyboard:
How can i add items to ChipTextView from code so that it seems like i had added them by using keyboard ?
I found the solution to my problem on TokenAutoComplete github documentation. I don't know how i missed this before but i have finally found it. :)
For anyone who has faced the same problem i suggest you use addObject() method for adding items into ChipTextView.
I know that it is really easy to create a FileDownloader and call extend with a Button. But how do I start a download without the Button?
In my specific situation right now I have a ComboBox and the file I'd like to send to the user is generated after changing its value, based on the input. The file should be sent immediately without waiting for another click. Is that easily possible?
Thanks
raffael
I found a solution myself. Actually two.
The first one uses the deprecated method Page.open()
public class DownloadComponent extends CustomComponent implements ValueChangeListener {
private ComboBox cb = new ComboBox();
public DownloadComponent() {
cb.addValueChangeListener(this);
cb.setNewItemsAllowed(true);
cb.setImmediate(true);
cb.setNullSelectionAllowed(false);
setCompositionRoot(cb);
}
#Override
public void valueChange(ValueChangeEvent event) {
String val = (String) event.getProperty().getValue();
FileResource res = new FileResource(new File(val));
Page.getCurrent().open(res, null, false);
}
}
The javadoc here mentions some memory and security problems as reason for marking it deprecated
In the second I try to go around this deprecated method by registering the resource in the DownloadComponent. I'd be glad if a vaadin expert comments this solution.
public class DownloadComponent extends CustomComponent implements ValueChangeListener {
private ComboBox cb = new ComboBox();
private static final String MYKEY = "download";
public DownloadComponent() {
cb.addValueChangeListener(this);
cb.setNewItemsAllowed(true);
cb.setImmediate(true);
cb.setNullSelectionAllowed(false);
setCompositionRoot(cb);
}
#Override
public void valueChange(ValueChangeEvent event) {
String val = (String) event.getProperty().getValue();
FileResource res = new FileResource(new File(val));
setResource(MYKEY, res);
ResourceReference rr = ResourceReference.create(res, this, MYKEY);
Page.getCurrent().open(rr.getURL(), null);
}
}
Note: I do not really allow the user to open all my files on the server and you should not do that either. It is just for demonstration.
Here is my work-around. It works like a charm for me. Hope it will help you.
Create a button and hide it by Css (NOT by code: button.setInvisible(false))
final Button downloadInvisibleButton = new Button();
downloadInvisibleButton.setId("DownloadButtonId");
downloadInvisibleButton.addStyleName("InvisibleButton");
In your theme, add this rule to hide the downloadInvisibleButton:
.InvisibleButton {
display: none;
}
When the user clicks on menuItem: extend the fileDownloader to the downloadInvisibleButton, then simulate the click on the downloadInvisibleButton by JavaScript.
menuBar.addItem("Download", new MenuBar.Command() {
#Override
public void menuSelected(MenuBar.MenuItem selectedItem) {
FileDownloader fileDownloader = new FileDownloader(...);
fileDownloader.extend(downloadInvisibleButton);
//Simulate the click on downloadInvisibleButton by JavaScript
Page.getCurrent().getJavaScript()
.execute("document.getElementById('DownloadButtonId').click();");
}
});