I am trying to use the "Grid" vaadin component to simply display a list of POJO. To populate the POJO list I use the DataProvider.fromCallbacks and set a Spring service to it. The grid is correctly displayed but the content is empty. When I run the app in debug mode I can see that the callback method is never called, that's why the grid is empty.
It's a basic use of grid so i really don't understand why the callback isn't used.
Here is my layout :
#SpringComponent
#Route("")
#PWA(name = "Callcenter dashboard", shortName = "callcenter")
public class CallcenterConfigurationView extends VerticalLayout {
#Autowired
private ConfigurationController configurationControler;
private Grid<CallCenterModel> grid;
public CallcenterConfigurationView() {
this.grid = new Grid<CallCenterModel>();
grid.addColumn(CallCenterModel::getDescription).setHeader("Description");
add(grid);
setSizeFull();
}
#PostConstruct
public void initDataProvider() {
CallbackDataProvider<CallCenterModel, Void> dataProvider = DataProvider.fromCallbacks(
query -> configurationControler.findAllcenters().stream(),
query -> configurationControler.countAllcallcenters()
);
grid.setDataProvider(dataProvider);
}
I cannot try out your code as-is since I lack the implementations of ConfigurationController and CallCenterModel. When I fill in the blanks based on my own assumptions, I end up with something where the callback is indeed called. That causes another error, but that's a different story.
Some wild guesses for why the query method won't be called in your case:
Maybe the count callback returns 0? In that case, there's no need to fetch any items.
Maybe there's something that prevents initDataProvider from being run at all so that the grid doesn't use your data provider?
Maybe there's something else that assigns a different data provider to the grid after your data provider has been assigned?
Testing whether any of those things happen should be quite straightforward either by setting some breakpoints or by adding some logging.
Everything works works for me if I slightly adapt your code to abstract away the ConfigurationController part so that I don't need a database to run the example. (I've also done a couple of other small tweaks just to make the example a couple of lines shorter)
#SpringComponent
#Route("dashboard")
public class CallcenterConfigurationView extends VerticalLayout {
// My approximation of the relevant parts of CallCenterModel
public static class CallCenterModel {
private String description;
public CallCenterModel(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
// A fake "database" containing 100 instances
private static List<CallCenterModel> callCenterModels = IntStream.range(0, 100)
.mapToObj(index -> new CallCenterModel("Call center " + index))
.collect(Collectors.toList());
private Grid<CallCenterModel> grid = new Grid<>();
public CallcenterConfigurationView() {
grid.addColumn(CallCenterModel::getDescription).setHeader("Description");
add(grid);
setSizeFull();
}
#PostConstruct
public void initDataProvider() {
CallbackDataProvider<CallCenterModel, Void> dataProvider = DataProvider.fromCallbacks(
query -> callCenterModels
.subList(query.getOffset(), query.getOffset() + query.getLimit())
.stream(),
query -> callCenterModels.size());
grid.setDataProvider(dataProvider);
}
}
Related
I have a lot of classes implementing a "common" interface called Setter.
public interface Setter {
Result set(Config config, int entityId);
enum Result {
HANDLED, HANDLING_ERROR, REJECTED
}
}
An example of an implementation looks like this, it sets in the world a 'number' value for a given 'entity' distinguished by its entityId:
public class NumberSetter implements Setter {
private World world;
private Assets assets;
public NumberSetter(World world, Assets assets) {
this.world = world.
this.assets = assets;
}
#Override
public Result set(Config config, int entityId) {
if (config instanceof NumberConfig numberConfig) {
world.passNumber(entityId, numberConfig.number);
return Result.HANDLED;
} else {
return Result.REJECTED;
}
}
}
Please do notice, that the Config object is cast to a specific NumberConfig, otherwise the Setter implementation signals it didn't handle the argument.
I am using a Set of these Setters in a network-enabled class, where it tries to match a super-type Config object against one of these Setters from the Set. (The naming might be subject to change lmao.) The code below handles a network package by passing it to all of the Setters in the Set and checks if there were any errors or if no Setter handled the package. If the check passes then the package wasn't handled properly and the Handler returns a NOT_HANDLED which later crashes the program because I'm still at the development stage.
public class ConfigNetworkHandler implements NetworkHandler {
private final Assets assets;
private final Set<Setter> setterSet;
public ConfigNetworkHandler(
Assets assets,
Set<Setter> setterSet
) {
this.assets = assets;
this.setterSet = setterSet;
}
#Override
public boolean handle(WebSocket webSocket, int worldEntity, Component component) {
var configId = ((ConfigId) component).getId();
var config = assets.getConfigs().get(configId);
var setterResults = setterSet.stream()
.map(setter -> setter.set(config, worldEntity))
.toList();
var anyErrors = setterResults.stream().anyMatch(HANDLING_ERROR::equals);
var wasHandled = setterResults.stream().anyMatch(HANDLED::equals);
if (anyErrors || !wasHandled) {
return NOT_HANDLED;
}
return FULLY_HANDLED;
}
}
I don't like it how I am not using Java's type system properly. I don't know how to do it otherwise, without manually providing a Map between ConfigId's and the Setters, which I would rather not do, because the ConfigIds aren't known at compile-time. The NetworkHandler-type-stuff is kind of similar but there are a lot less of them and they will probably be refactored in a similar way (there is also a lot fewer of them, so it's not a practical issue).
I like the current solution because it allows me to add and remove Setters without worrying about the other ones and also I don't need to change the implementation of ConfigNetworkHandler, because it's provided a Set. I don't like it, because it requires list traversing, doesn't seem "idiomatic" for Java, returns weird Results instead of just not being called because it doesn't accept the type, and FEELS like there should be something else.
Do you have an idea how to approach this differently?
I'm new to Vaadin and trying to understand how to make View to get several parameters from URL.
For example
http://www.some.com/book/18/page/41
Numbers 18 and 41 are parameters.
I've found that I can implement HasUrlParameter<T> and then use setParameter method, but it can be used only for one parameter.
Are you using #WildcardParameter in your setParameter method? Wildcard URL parameters
Assuming that greet (The book in your case) is the route, then the code below sets 18\page\41. Since it's a string you would need to parse it and extract values you need, but the value is there.
#Route("greet")
public class WildcardGreeting extends Div
implements HasUrlParameter<String> {
#Override
public void setParameter(BeforeEvent event,
#WildcardParameter String parameter) {
if (parameter.isEmpty()) {
setText("Welcome anonymous.");
} else {
setText(String.format(
"Handling parameter %s.",
parameter));
}
}
}
P.S. Not related to the question, but looking at your URL, could it be that query parameters suit you better Query parameters?
There is no built-in suppor for having multiple parameters for Java views in Vaadin. What you can do is to annotate the parameter with #WildcardParameter so that multiple path segments can be captured into one parameter. You would then have to manually manage the contents of that value - concatenating strings when generating URLs and parsing strings in setParameter.
Support for multiple parameters is being worked on right now, but the work is not yet completed. It is not yet clear which future version of Vaadin will get this feature, but my guess right now is that it would be either version 14.3 or 14.4.
It seems like Vaadin 14 has got an update and got support for multiple path parameters.
Example:
#Route("user/:userID/:messageID/edit")
public class UserProfileEdit extends Div implements BeforeEnterObserver {
private String userID;
private String messageID;
#Override
public void beforeEnter(BeforeEnterEvent event) {
userID = event.getRouteParameters().get("userID").get();
messageID = event.getRouteParameters().get("messageID").get();
}
}
Source: https://vaadin.com/docs/v14/flow/routing/tutorial-router-templates
A simple example with the solution
#Route("book")
public class BookView extends Div implements HasUrlParameter<String> {
#Override
public void setParameter(BeforeEvent event, #WildcardParameter String parameter) {
if (!parameter.isEmpty()) {
String params[] = parameter.split("/");
if (params.length == 1) {
// Do something ..
} else if (params.length == 2) {
// Do another thing ..
} else {
// Do something else
}
}
}
}
The link can be created like this:
new RouterLink("No params", BookView.class);
new RouterLink("One param", BookView.class, "18");
new RouterLink("Two param", BookView.class, "18/edit");
I've saw a video where is possible to set named locators for allure report
to get view $(locatorname).click - passed:
There is code:
public class Named extends NamedBy {
private final By origin;
private String name;
public Named(By origin) {
this.origin = origin;
}
public Named as(String name) {
this.name = name;
}
#Override
public String toString() {
return Objects.nonNull(name) ? name : this.origin.toString();
}
#Override
public List<WebElement> findElements(SearchContext context) {
return new Named(By.id(id));
}
}
And code for elements:
SelenideElement button = $(id("someid").**as("locatorName")**)
and then should be possible to work with this element.
But i can't.
I dont have method as when i try to create selenideElement.
Pls help. such report is mush more readble.
video URL: https://youtu.be/d5gjK6hZHE4?t=1300
Your example doesn't seem to be valid. At least, a method as must return this. Moreover, id in the overridden findElements is missing. Plus, it's not really clear why you extend NamedBy instead of By.
Anyway, that's just a wrapper around By. To see those locators' names in report you have to follow a previous example in a video first (event listener), before completing NamedBy implementation.
P.S. To make it works the same way as was introduced in the code snippet, you have to add an additional creational logic, e.g.:
public static NamedBy id(String locator) {
return new NamedBy(By.id(locator));
}
Well, I changed the description, I think it would be clearer.
My application is divided into two parts (information on the right (1) and information on the left (2))
(2) - Here I have a table where I choose a contract.
(1) - The information that relates to the selected contract is displayed here, all shortcuts work correctly, but the information in the second table, which is in this part of the application (on the right), is not displayed, in this table I want to write down the links to the files that relate to the document which I chose on the left side
(Now while I use the type String, then I will do it under local links)
//I removed all unnecessary variables in my opinion that are displayed correctly.
public class MainData {
private final List<StringProperty> nameLink;
public MainData() {
this.nameLink = FXCollections.observableArrayList();
}
public List<StringProperty> getNameLink() {
return nameLink;
}
public List<StringProperty> setNameLink(StringProperty nameLink) {
this.nameLink.add(nameLink);
return getNameLink();
}
}
public class MainController {
private TableColumn<MainData, String> contractColumn;
public MainController() {
}
#FXML
private void initialize() {
contractColumn.setCellValueFactory(cellData -> cellData.getValue().getNameLink());
}
}
The problem is Initialize, lambda is waiting for the value ObservableValue, but I am passing List<String>, but if I change the value to List<String>, then the method getNameLink does not work for me how to fix it? How to make a wrapper?
This has baffled me for a while now and I cannot seem to get the grasp of it. I'm using Cell Value Factory to populate a simple one column table and it does not populate in the table.
It does and I click the rows that are populated but I do not see any values in them- in this case String values. [I just edited this to make it clearer]
I have a different project under which it works under the same kind of data model. What am I doing wrong?
Here's the code. The commented code at the end seems to work though. I've checked to see if the usual mistakes- creating a new column instance or a new tableview instance, are there. Nothing. Please help!
//Simple Data Model
Stock.java
public class Stock {
private SimpleStringProperty stockTicker;
public Stock(String stockTicker) {
this.stockTicker = new SimpleStringProperty(stockTicker);
}
public String getstockTicker() {
return stockTicker.get();
}
public void setstockTicker(String stockticker) {
stockTicker.set(stockticker);
}
}
//Controller class
MainGuiController.java
private ObservableList<Stock> data;
#FXML
private TableView<Stock> stockTableView;// = new TableView<>(data);
#FXML
private TableColumn<Stock, String> tickerCol;
private void setTickersToCol() {
try {
Statement stmt = conn.createStatement();//conn is defined and works
ResultSet rsltset = stmt.executeQuery("SELECT ticker FROM tickerlist order by ticker");
data = FXCollections.observableArrayList();
Stock stockInstance;
while (rsltset.next()) {
stockInstance = new Stock(rsltset.getString(1).toUpperCase());
data.add(stockInstance);
}
} catch (SQLException ex) {
Logger.getLogger(WriteToFile.class.getName()).log(Level.SEVERE, null, ex);
System.out.println("Connection Failed! Check output console");
}
tickerCol.setCellValueFactory(new PropertyValueFactory<Stock,String>("stockTicker"));
stockTableView.setItems(data);
}
/*THIS, ON THE OTHER HAND, WORKS*/
/*Callback<CellDataFeatures<Stock, String>, ObservableValue<String>> cellDataFeat =
new Callback<CellDataFeatures<Stock, String>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(CellDataFeatures<Stock, String> p) {
return new SimpleStringProperty(p.getValue().getstockTicker());
}
};*/
Suggested solution (use a Lambda, not a PropertyValueFactory)
Instead of:
aColumn.setCellValueFactory(new PropertyValueFactory<Appointment,LocalDate>("date"));
Write:
aColumn.setCellValueFactory(cellData -> cellData.getValue().dateProperty());
For more information, see this answer:
Java: setCellValuefactory; Lambda vs. PropertyValueFactory; advantages/disadvantages
Solution using PropertyValueFactory
The lambda solution outlined above is preferred, but if you wish to use PropertyValueFactory, this alternate solution provides information on that.
How to Fix It
The case of your getter and setter methods are wrong.
getstockTicker should be getStockTicker
setstockTicker should be setStockTicker
Some Background Information
Your PropertyValueFactory remains the same with:
new PropertyValueFactory<Stock,String>("stockTicker")
The naming convention will seem more obvious when you also add a property accessor to your Stock class:
public class Stock {
private SimpleStringProperty stockTicker;
public Stock(String stockTicker) {
this.stockTicker = new SimpleStringProperty(stockTicker);
}
public String getStockTicker() {
return stockTicker.get();
}
public void setStockTicker(String stockticker) {
stockTicker.set(stockticker);
}
public StringProperty stockTickerProperty() {
return stockTicker;
}
}
The PropertyValueFactory uses reflection to find the relevant accessors (these should be public). First, it will try to use the stockTickerProperty accessor and, if that is not present fall back to getters and setters. Providing a property accessor is recommended as then you will automatically enable your table to observe the property in the underlying model, dynamically updating its data as the underlying model changes.
put the Getter and Setter method in you data class for all the elements.