I am trying to develop a demo app using JavaFX where I have 2 controllers MainController and AddUserController, each of them has its own .fxml file, main.fxml contains table that displays the list of users, and a button that opens the window for adding user with some fields. So the list of users is contained in a in-memory DB.
So when adding an user the table isn't filled with the list.
MainController.java
public void setTestUser(){
List<Users> users = new ArrayList();
Users user = new Users();
user.setName("Name");
user.setSurname("Surname");
users.add(user);
name.setCellValueFactory(new PropertyValueFactory<Users, String>("name"));
surname.setCellValueFactory(new PropertyValueFactory<Users, String>("surname"));
usersTable.setItems(FXCollections.observableArrayList(users));
}
AddUserController.java
private void handleAddUser(ActionEvent event) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.load(getClass().getResource("/fxml/main.fxml").openStream());
MainController mainController = fxmlLoader.getController();
Users user = new Users();
user.setName(name.getText());
user.setSurname(surname.getText());
user.setDateOfBirth(dateOfBirth.getValue().toString());
usersService.add(user);
mainController.populateUsersTable();
}
After adding the user the table isn't updated, but if I try to usersTable.getItems(); the list is there, what's the problem so far?
// UPDATE:
Trying to execute this from MainController.java and it is working OK
public void setTestUser(){
List<Users> users = new ArrayList();
Users user = new Users();
user.setName("Name");
user.setSurname("Surname");
users.add(user);
name.setCellValueFactory(new PropertyValueFactory<Users, String>("name"));
surname.setCellValueFactory(new PropertyValueFactory<Users, String>("surname"));
usersTable.setItems(FXCollections.observableArrayList(users));
}
The reason might be that you create a new controller on a new pane each time you handle an "add user" event. This is plainly wrong, as changes to it don't affect the pane which is actually displayed.
Instead, you should get the controller when you load your pane, store it somewhere and refer to it whenever you need it.
FXMLLoader myLoader = new FXMLLoader(
getClass().getResource(screen.getFile()),
resources);
Parent loadScreen = myLoader.load();
XYController myScreenController = myLoader.getController();
Related
I'm trying to refresh grid after adding an row into it, but it's not working. Here is my Event Listener on my UI Code : Edited Full code.
Grid<TransactionModel> transactionData = new Grid<>(TransactionModel.class);
try {
// transactionData.setItems(transactionServices.getTransactionTable());
List<TransactionModel> transactionList = transactionServices.getTransactionTable();
ListDataProvider<TransactionModel> transactionDataProvider = new ListDataProvider<>(transactionList);
transactionData.setDataProvider(transactionDataProvider);
transactionData.setColumns("id", "transactionTimestamp", "srcAccountId", "dstAccountId", "amount");
Grid.Column<TransactionModel> idColumn = transactionData.getColumnByKey("id");
Grid.Column<TransactionModel> srcAccountIdColumn = transactionData.getColumnByKey("srcAccountId");
Grid.Column<TransactionModel> dstAccountIdColumn = transactionData.getColumnByKey("dstAccountId");
HeaderRow filterRow2 = transactionData.appendHeaderRow();
TransactionFilterModel transactionFilterModel = new TransactionFilterModel();
transactionDataProvider.setFilter(transaction -> transactionFilterModel.find(transaction));
// Filter srcAccountId
TextField idField = new TextField();
idField.addValueChangeListener(event -> {
transactionFilterModel.setId(event.getValue());
transactionDataProvider.refreshAll();
});
idField.setValueChangeMode(ValueChangeMode.EAGER);
filterRow2.getCell(idColumn).setComponent(idField);
idField.setSizeFull();
idField.getElement().setAttribute("focus-terget", "");
// Filter srcAccountId
TextField srcAccountIdField = new TextField();
srcAccountIdField.addValueChangeListener(event -> {
transactionFilterModel.setSrcAccountId(event.getValue());
transactionDataProvider.refreshAll();
});
srcAccountIdField.setValueChangeMode(ValueChangeMode.EAGER);
filterRow2.getCell(srcAccountIdColumn).setComponent(srcAccountIdField);
srcAccountIdField.setSizeFull();
srcAccountIdField.getElement().setAttribute("focus-terget", "");
// Filter dstAccountId
TextField dstAccountIdField = new TextField();
dstAccountIdField.addValueChangeListener(event -> {
transactionFilterModel.setDstAccountId(event.getValue());
transactionDataProvider.refreshAll();
});
dstAccountIdField.setValueChangeMode(ValueChangeMode.EAGER);
filterRow2.getCell(dstAccountIdColumn).setComponent(dstAccountIdField);
dstAccountIdField.setSizeFull();
dstAccountIdField.getElement().setAttribute("focus-terget", "");
transactionData.setWidth("50%");
} catch (JsonProcessingException | EndpointException ex) {
Logger.getLogger(MainView.class.getName()).log(Level.SEVERE, null, ex);
}
// Event Listener
submitButton.addClickListener(e -> {
System.out.println("Submitted !");
AccountModel submittedModel = new AccountModel();
if (accountModelBinder.writeBeanIfValid(submittedModel)) {
try {
accountServices.registerAccount(submittedModel);
accountIdTextField.clear();
nameTextField.clear();
addressTextField.clear();
birthDateDatePicker.clear();
allowNegativeBalanceButtonGroup.clear();
} catch (EndpointException | JsonProcessingException ez) {
Logger.getLogger(MainView.class.getName()).log(Level.SEVERE, null, ez);
}
}
accountData.getDataProvider().refreshAll(); // <- REFRESH
});
And for service I'm using rest, here is the accountservice code:
public List<AccountModel> getAccountTable() throws JsonProcessingException, EndpointException {
List<AccountModel> datalog = new JsonResponseReader(restMockvaEndpoint.send(new EndpointRequestBuilder()
.method("GET")
.resource("/account")
.build()
)).getContentTable(AccountModel.class).getData();
return datalog;
}
public AccountModel registerAccount(AccountModel accountModel) throws JsonProcessingException, EndpointException{
AccountModel account = new JsonResponseReader(restMockvaEndpoint.send(new EndpointRequestBuilder()
.method("POST")
.content(Json.getWriter().writeValueAsBytes(accountModel), MediaType.APPLICATION_JSON)
.resource("/account")
.build())).getContentObject(AccountModel.class);
return account;
}
Edited : Add registerAccount.
The problem is when I click submitButton for adding new data, the grid doesn't refresh. Any ideas?
For this to work with a ListDataProvider, you would have to modify the underlying list (add/remove items).
Now that you call refreshAll(), it just reads the list you passed again, and as it still contains the same items, nothing changes. It does not know to fetch the items from your service again.
There are a few solutions that I can think of:
1. Manually add the new item to the list (it will then appear at the end of the grid):
accountList.add(submittedModel);
...
// This instructs the grid to read the list again
accountData.getDataProvider().refreshAll();
If your accountServices.registerAccount method returns the newly saved item, you might want to add that one instead.
2. Set the items again
You could fetch the items again and set a new data provider. You can just use setItems(...) then, which uses a ListDataProvider under the hood.
// Run this both when first creating the grid, and again after the new item is saved.
// This time you don't need to call refreshAll()
List<AccountModel> accountList = accountServices.getAccountTable();
accountData.setItems(accountList);
3. Use a lazy data provider
When you use a lazy data provider, for example from callbacks, then calling refreshAll() executes those callbacks again to fetch new items.
In this case you need to implement the needed service methods, and it requires a bit more work if you need sorting or filtering.
this.setDataProvider(DataProvider.fromCallbacks(
query -> myAccountService.getAccounts(query.getOffset(), query.getLimit()).stream(),
query -> myAccountService.countAccounts()
));
I choose Vaadin for building my very simple UI within the spring boot application.
I have:
#Route
#PWA(name = "Signing certificates manager", shortName = "CertMgr")
public class MainView extends VerticalLayout {
public MainView() {
Tab tab1 = new Tab("Certificates");
Tab tab2 = new Tab("Users");
Tabs tabs = new Tabs(tab1, tab2);
CertView certView = new CertView();
certView.setVisible(false);
UserView userView = new UserView(addUsers());
userView.setVisible(true);
Map<Tab, Component> tabsToPages = new HashMap<>();
tabsToPages.put(tab1, certView);
tabsToPages.put(tab2, userView);
Div pages = new Div(certView, userView);
Set<Component> pagesShown = Stream.of(userView)
.collect(Collectors.toSet());
tabs.addSelectedChangeListener(event -> {
pagesShown.forEach(page -> page.setVisible(false));
pagesShown.clear();
Component selectedPage = tabsToPages.get(tabs.getSelectedTab());
selectedPage.setVisible(true);
pagesShown.add(selectedPage);
});
add(tabs, pages);
}
private List<User> addUsers() {
return new LinkedList<User>() {{
add(new User("qewr", "asdf", "xzcv"));
}};
}
}
and
public class UserView extends VerticalLayout {
Grid<User> grid;
public UserView(List<User> users) {
grid = new Grid<>(User.class);
grid.setItems(users);
grid.addColumn(User::getMail).setHeader("mail");
grid.addColumn(User::getName).setHeader("name");
grid.addColumn(User::getPass).setHeader("pass");
grid.addSelectionListener(event -> {
List<User> selected = (List<User>) event.getAllSelectedItems();
add(new UserRow(selected.get(0)));
});
add(grid);
}
}
and the result of this is a view like this:
so you can see, the grid has no width and is too long (I have only one user there)
I am new to Vaadin so I am surely doing something wrong, but basically this is vaadin tabs handling from the samples.
My questions are:
how do I draw the grid properly with my current setup?
is it possible to display a view from completely different page when clicking on tab with that page would have it's own context and could use spring's DI? like to have autonomous page and not coupled objects like I have now. or advice the best pattern to handle this with vaadin, please.
Vaadin: 12.0.7
Spring-Boot: 2.1.2.RELEASE
Hope you get my point.
Thanks for advices!
just do
Div pages = new Div(certView, userView);
pages.setSizeFull();
I am building a football league management system, I built the user interface using javaFx, I created this class to populate the table using a database.
public class TableHandler {
public static ObservableList<Team> getTeams() {
ObservableList<Team> list = FXCollections.observableArrayList();
DBConnection db;
try {
db = new DBConnection();
String sql = "Select * from teams";
ResultSet result = db.read(sql);
while (result.next()) {
list.add(new Team(result.getInt(1), result.getString(2), result.getString(3), result.getInt(4),
result.getDouble(5)));
}
} catch (Exception e) {
e.getMessage();
}
return list;
}
public static TableView<Team> getTable(ObservableList<Team> list) {
TableView<Team> table;
TableColumn<Team, String> idColumn = new TableColumn<>("ID");
idColumn.setCellValueFactory(new PropertyValueFactory<>("id"));
TableColumn<Team, String> nameColumn = new TableColumn<>("Name");
nameColumn.setCellValueFactory(new PropertyValueFactory<>("name"));
TableColumn<Team, String> phoneNumberColumn = new TableColumn<>("phoneNumber");
phoneNumberColumn.setCellValueFactory(new PropertyValueFactory<>("phoneNumber"));
TableColumn<Team, Integer> pointsColumn = new TableColumn<>("Points");
pointsColumn.setCellValueFactory(new PropertyValueFactory<>("points"));
TableColumn<Team, Double> budgetColumn = new TableColumn<>("Budget");
budgetColumn.setCellValueFactory(new PropertyValueFactory<>("budget"));
table = new TableView<>();
table.setItems(list);
table.getColumns().addAll(idColumn, nameColumn, phoneNumberColumn, pointsColumn, budgetColumn);
return table;
}
and I created a button to add teams to the table by the user, what I can't figuer out is how to refresh the table when the user hit the add button, any help would be appriciated.
You don't have to. The very idea of an observable list is that the TableView observes for changes in it and renders the value change accordingly.
The thing you have to make sure of is that you're adding elements to the collection that was actually bound to the TableView and not some other one. You didn't post the code that adds the items, so it's hard to tell, but if you're using getTeams() and then adding to that, then it's wrong (since it's a new ObservableList and not the one bound to the TableView). You should always be using table.getItems().add(...) to add items to a TableView.
I have a form with the following logic:
TextField name = new TextField<>("name", Model.of(""));
TextField surname = new TextField<>("surname", Model.of(""));
TextField mobile = new TextField<>("mobile", Model.of(""));
TextField phone = new TextField<>("phone", Model.of(""));
HiddenField id = new HiddenField<>("id", Model.of(""));
EmailTextField email = new EmailTextField("email", Model.of(""));
Form form = new Form("formContact") {
#Override
protected void onSubmit() {
super.onSubmit();
Contact contact = new Contact();
contact.setName(name.getValue());
contact.setEmail(email.getValue());
contact.setSurname(surname.getValue());
contact.setMobile(mobile.getValue());
contact.setPhone(phone.getValue());
service.save(contact);
}
};
form.add(id);
form.add(email.setRequired(false));
form.add(name.setRequired(true));
form.add(surname.setRequired(true));
form.add(mobile.setRequired(true));
form.add(phone.setRequired(false));
add(form);
I use that code when a client wants to insert a new Contact, and it works.
What I need now is to handle the update of an existing Contact, so I just need to fill an existing form with values from a known Contact instance:
Contact contact = service.get(1);
How can I do that?
Thanks
I would use CompoundPropertyModel for form so it will update when model is changing and also don't need set data to fields. Send model when you creating page or model, you can send contract instance(even empty one). Let's say your class name is MyPanel, then constructor
MyPanel(String id, IModel<Contract> model) {
super(id, model);
}
Now when you are creating form you can use CompoundPropertyModel benefits(in Contract class should be fields name, surname, mobile,etc with public getters and setters)
#Override
protected void onInitialize() {
super.onInitialize();
Form<Contract> form = new Form("formContact", new CompoundPropertyModel(getModel()){
#Override
protected void onSubmit() {
super.onSubmit();
service.save(getModelObject());
}
});
add(form);
form.add(new TextField<>("name").setRequired(true));
form.add(new TextField<>("surname").setRequired(true));
form.add(new TextField<>("mobile").setRequired(true));
form.add(new TextField<>("phone").setRequired(false));
form.add(new HiddenField<>("id"));
form.add(new EmailTextField("email").setRequired(false));
Let's update contract by button clicking
form.add(new AjaxLink<Void>("updateContract"){
#Override
public void onClick(AjaxRequestTarget target) {
form.setModelObject(service.get(1));
target.add(form);
}
});
You should use the existing contact's data in the models of the form components.
E.g. TextField name = new TextField<>("name", new PropertyModel(contact, "name"));
Also see CompoundPropertyModel.
According to the logic of the application, I have a controller to remove the recycle bin. By the link we get the session id, by it we search for the user in the database and delete all the invoices associated with it.
#RequestMapping(path="/basket/del/{sessId}", method=RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public BasketListReply delAllProducts(#PathVariable String sessId){
BasketListReply rep = new BasketListReply();
try {
//check if session exist
List<Long> delList = userService.findInvoiceBySessionId(sessId);
//delList contain list of invoices id to delete
if(delList != null) {
for(int i=0;i<delList.size();i++){
invoiceService.delInvoice(delList.get(i));
}
}
}catch(Exception e){
rep.retcode = -1;
rep.error_message = e.getMessage();
}
return rep;
}
With method findInvoiceBySessionId() I get list of invoices from collection () to delete.
public List findInvoiceBySessionId(String sessId) throws Exception{
List list = new ArrayList();
List<Appuser> users = findUserBySessionId(sessId);
if(!users.isEmpty()) {
//if exist, then extract invoices for this user
Appuser u = users.get(0);
List<Invoice> invoices = (List<Invoice>)u.getInvoiceCollection();
for(Invoice inv:invoices) {
list.add(inv.getOrderId());
}
}
return list;
}
Method delInvoice() I use to delete invoice from repository.
public void delInvoice(Long orderId){
if(invoiceRepository.findOne(orderId) == null) {
return;
}
invoiceRepository.delete(orderId);
}
File Appuser.java (field that contain collection of Invoices):
#OneToMany(cascade = CascadeType.ALL, mappedBy = "userId")
private Collection<Invoice> invoiceCollection;
Did I understand correctly that I can not in one controller and extract data from the database via JPA and delete the same data ... since the deletion should go in a separate transaction.
Or am I doing something wrong?
When I delete the data by redirecting the request to the controller to delete the data, everything works fine.