Added primitive pagination controls to transaction view, with expandability for other paginated entities.
This commit is contained in:
parent
633cd60572
commit
01d08154e0
|
@ -0,0 +1,7 @@
|
|||
package com.andrewlalis.perfin;
|
||||
|
||||
public record Pair<A, B>(A first, B second) {
|
||||
public static <A, B> Pair<A, B> of(A first, B second) {
|
||||
return new Pair<>(first, second);
|
||||
}
|
||||
}
|
|
@ -10,6 +10,17 @@ import java.net.URL;
|
|||
import java.util.function.Consumer;
|
||||
|
||||
public class SceneUtil {
|
||||
public static <N, C> Pair<N, C> loadNodeAndController(String fxml) {
|
||||
FXMLLoader loader = new FXMLLoader(SceneUtil.class.getResource(fxml));
|
||||
try {
|
||||
N node = loader.load();
|
||||
C controller = loader.getController();
|
||||
return Pair.of(node, controller);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> Parent loadNode(String fxml, Consumer<T> controllerConfig) {
|
||||
FXMLLoader loader = new FXMLLoader(SceneUtil.class.getResource(fxml));
|
||||
try {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package com.andrewlalis.perfin.control;
|
||||
|
||||
import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
|
||||
import com.andrewlalis.perfin.data.CurrencyUtil;
|
||||
import com.andrewlalis.perfin.data.DateUtil;
|
||||
import com.andrewlalis.perfin.model.CreditAndDebitAccounts;
|
||||
|
@ -23,7 +22,7 @@ import java.util.List;
|
|||
|
||||
import static com.andrewlalis.perfin.PerfinApp.router;
|
||||
|
||||
public class TransactionViewController implements RouteSelectionListener {
|
||||
public class TransactionViewController {
|
||||
private Transaction transaction;
|
||||
|
||||
@FXML public Label amountLabel;
|
||||
|
@ -37,9 +36,9 @@ public class TransactionViewController implements RouteSelectionListener {
|
|||
@FXML public HBox attachmentsHBox;
|
||||
private final ObservableList<TransactionAttachment> attachmentsList = FXCollections.observableArrayList();
|
||||
|
||||
@Override
|
||||
public void onRouteSelected(Object context) {
|
||||
this.transaction = (Transaction) context;
|
||||
public void setTransaction(Transaction transaction) {
|
||||
this.transaction = transaction;
|
||||
if (transaction == null) return;
|
||||
amountLabel.setText(CurrencyUtil.formatMoney(transaction.getAmount(), transaction.getCurrency()));
|
||||
timestampLabel.setText(DateUtil.formatUTCAsLocalWithZone(transaction.getTimestamp()));
|
||||
descriptionLabel.setText(transaction.getDescription());
|
||||
|
@ -75,6 +74,7 @@ public class TransactionViewController implements RouteSelectionListener {
|
|||
new Label(attachment.getFilename()),
|
||||
new Label(attachment.getContentType())
|
||||
);
|
||||
// TODO: Custom attachment preview element.
|
||||
return vbox;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,23 +1,82 @@
|
|||
package com.andrewlalis.perfin.control;
|
||||
|
||||
import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
|
||||
import com.andrewlalis.perfin.Pair;
|
||||
import com.andrewlalis.perfin.SceneUtil;
|
||||
import com.andrewlalis.perfin.control.component.DataSourcePaginationControls;
|
||||
import com.andrewlalis.perfin.control.component.TransactionTile;
|
||||
import com.andrewlalis.perfin.data.pagination.Page;
|
||||
import com.andrewlalis.perfin.data.pagination.PageRequest;
|
||||
import com.andrewlalis.perfin.data.pagination.Sort;
|
||||
import com.andrewlalis.perfin.model.Profile;
|
||||
import com.andrewlalis.perfin.model.Transaction;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import static com.andrewlalis.perfin.PerfinApp.router;
|
||||
|
||||
public class TransactionsViewController implements RouteSelectionListener {
|
||||
@FXML
|
||||
public VBox transactionsVBox;
|
||||
@FXML public BorderPane transactionsListBorderPane;
|
||||
@FXML public VBox transactionsVBox;
|
||||
private DataSourcePaginationControls paginationControls;
|
||||
|
||||
|
||||
@FXML public VBox detailPanel;
|
||||
private final ObjectProperty<Transaction> selectedTransaction = new SimpleObjectProperty<>(null);
|
||||
|
||||
@FXML public void initialize() {
|
||||
// Initialize the left-hand paginated transactions list.
|
||||
this.paginationControls = new DataSourcePaginationControls(
|
||||
transactionsVBox.getChildren(),
|
||||
new DataSourcePaginationControls.PageFetcherFunction() {
|
||||
@Override
|
||||
public Page<? extends Node> fetchPage(PageRequest pagination) throws Exception {
|
||||
try (var repo = Profile.getCurrent().getDataSource().getTransactionRepository()) {
|
||||
return repo.findAll(pagination).map(TransactionsViewController.this::makeTile);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalCount() throws Exception {
|
||||
try (var repo = Profile.getCurrent().getDataSource().getTransactionRepository()) {
|
||||
return (int) repo.countAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
transactionsListBorderPane.setBottom(paginationControls);
|
||||
|
||||
// Initialize the right-hand transaction detail view.
|
||||
HBox container = (HBox) detailPanel.getParent();
|
||||
ObservableValue<Double> halfWidthProp = container.widthProperty().map(n -> n.doubleValue() * 0.5);
|
||||
detailPanel.minWidthProperty().bind(halfWidthProp);
|
||||
detailPanel.maxWidthProperty().bind(halfWidthProp);
|
||||
detailPanel.prefWidthProperty().bind(halfWidthProp);
|
||||
detailPanel.managedProperty().bind(detailPanel.visibleProperty());
|
||||
detailPanel.visibleProperty().bind(selectedTransaction.isNotNull());
|
||||
|
||||
Pair<BorderPane, TransactionViewController> detailComponents = SceneUtil.loadNodeAndController("/transaction-view.fxml");
|
||||
TransactionViewController transactionViewController = detailComponents.second();
|
||||
BorderPane transactionDetailView = detailComponents.first();
|
||||
transactionDetailView.managedProperty().bind(transactionDetailView.visibleProperty());
|
||||
transactionDetailView.visibleProperty().bind(selectedTransaction.isNotNull());
|
||||
detailPanel.getChildren().add(transactionDetailView);
|
||||
selectedTransaction.addListener((observable, oldValue, newValue) -> {
|
||||
transactionViewController.setTransaction(newValue);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRouteSelected(Object context) {
|
||||
refreshTransactions();
|
||||
paginationControls.sorts.setAll(Sort.desc("timestamp"));
|
||||
this.paginationControls.setPage(1);
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
@ -25,13 +84,37 @@ public class TransactionsViewController implements RouteSelectionListener {
|
|||
router.navigate("create-transaction");
|
||||
}
|
||||
|
||||
private void refreshTransactions() {
|
||||
Thread.ofVirtual().start(() -> {
|
||||
Profile.getCurrent().getDataSource().useTransactionRepository(repo -> {
|
||||
var page = repo.findAll(PageRequest.unpaged(Sort.desc("timestamp")));
|
||||
var components = page.items().stream().map(transaction -> new TransactionTile(transaction, this::refreshTransactions)).toList();
|
||||
Platform.runLater(() -> transactionsVBox.getChildren().setAll(components));
|
||||
});
|
||||
});
|
||||
private TransactionTile makeTile(Transaction transaction) {
|
||||
var tile = new TransactionTile(transaction);
|
||||
tile.setOnMouseClicked(event -> {
|
||||
if (selectedTransaction.get() == null || selectedTransaction.get().getId() != transaction.getId()) {
|
||||
selectedTransaction.set(transaction);
|
||||
} else {
|
||||
selectedTransaction.set(null);
|
||||
}
|
||||
});
|
||||
tile.selected.bind(selectedTransaction.map(t -> t != null && t.getId() == transaction.getId()));
|
||||
return tile;
|
||||
}
|
||||
|
||||
// private void refreshTransactions() {
|
||||
// Thread.ofVirtual().start(() -> {
|
||||
// Profile.getCurrent().getDataSource().useTransactionRepository(repo -> {
|
||||
// var page = repo.findAll(PageRequest.unpaged(Sort.desc("timestamp")));
|
||||
// var components = page.items().stream().map(transaction -> {
|
||||
// var tile = new TransactionTile(transaction, this::refreshTransactions);
|
||||
// tile.setOnMouseClicked(event -> {
|
||||
// if (selectedTransaction.get() == null || selectedTransaction.get().getId() != transaction.getId()) {
|
||||
// selectedTransaction.set(transaction);
|
||||
// } else {
|
||||
// selectedTransaction.set(null);
|
||||
// }
|
||||
// });
|
||||
// tile.selected.bind(selectedTransaction.map(t -> t != null && t.getId() == transaction.getId()));
|
||||
// return tile;
|
||||
// }).toList();
|
||||
// Platform.runLater(() -> transactionsVBox.getChildren().setAll(components));
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
package com.andrewlalis.perfin.control.component;
|
||||
|
||||
import com.andrewlalis.perfin.data.pagination.Page;
|
||||
import com.andrewlalis.perfin.data.pagination.PageRequest;
|
||||
import com.andrewlalis.perfin.data.pagination.Sort;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
|
||||
/**
|
||||
* A pane that contains some controls for navigating a paginated data source.
|
||||
* That includes going to the next/previous page, setting the preferred page
|
||||
* size.
|
||||
*/
|
||||
public class DataSourcePaginationControls extends BorderPane {
|
||||
public interface PageFetcherFunction {
|
||||
Page<? extends Node> fetchPage(PageRequest pagination) throws Exception;
|
||||
default int getTotalCount() throws Exception {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public final IntegerProperty currentPage = new SimpleIntegerProperty(1);
|
||||
public final IntegerProperty maxPages = new SimpleIntegerProperty(-1);
|
||||
public final IntegerProperty itemsPerPage = new SimpleIntegerProperty(5);
|
||||
public final ObservableList<Sort> sorts = FXCollections.observableArrayList();
|
||||
private final BooleanProperty fetching = new SimpleBooleanProperty(false);
|
||||
private final ObservableList<Node> target;
|
||||
private final PageFetcherFunction fetcher;
|
||||
|
||||
public DataSourcePaginationControls(ObservableList<Node> target, PageFetcherFunction fetcher) {
|
||||
this.target = target;
|
||||
this.fetcher = fetcher;
|
||||
|
||||
Text currentPageLabel = new Text();
|
||||
currentPageLabel.textProperty().bind(currentPage.asString());
|
||||
Text maxPagesLabel = new Text();
|
||||
maxPagesLabel.textProperty().bind(maxPages.asString());
|
||||
TextFlow maxPagesText = new TextFlow(new Text(" / "), maxPagesLabel);
|
||||
maxPagesText.managedProperty().bind(maxPagesText.visibleProperty());
|
||||
maxPagesText.visibleProperty().bind(maxPages.isNotEqualTo(-1));
|
||||
TextFlow pageText = new TextFlow(new Text("Page "), currentPageLabel, maxPagesText);
|
||||
|
||||
|
||||
Button previousPageButton = new Button("Previous Page");
|
||||
previousPageButton.disableProperty().bind(currentPage.lessThan(2).or(fetching));
|
||||
previousPageButton.setOnAction(event -> setPage(currentPage.get() - 1));
|
||||
Button nextPageButton = new Button("Next Page");
|
||||
nextPageButton.disableProperty().bind(fetching.or(currentPage.greaterThanOrEqualTo(maxPages)));
|
||||
nextPageButton.setOnAction(event -> setPage(currentPage.get() + 1));
|
||||
|
||||
sorts.addListener((ListChangeListener<Sort>) c -> {
|
||||
setPage(1);
|
||||
});
|
||||
|
||||
HBox hbox = new HBox(
|
||||
previousPageButton,
|
||||
pageText,
|
||||
nextPageButton
|
||||
);
|
||||
setCenter(hbox);
|
||||
}
|
||||
|
||||
public void setPage(int page) {
|
||||
try {
|
||||
fetching.set(true);
|
||||
PageRequest pagination = new PageRequest(page - 1, itemsPerPage.get(), sorts);
|
||||
var p = fetcher.fetchPage(pagination);
|
||||
int totalResults = fetcher.getTotalCount();
|
||||
target.setAll(p.items());
|
||||
if (totalResults != -1) {
|
||||
int max = totalResults / itemsPerPage.get();
|
||||
if (totalResults % itemsPerPage.get() != 0) {
|
||||
max += 1;
|
||||
}
|
||||
maxPages.set(max);
|
||||
}
|
||||
currentPage.set(page);
|
||||
} catch (Exception e) {
|
||||
target.clear();
|
||||
e.printStackTrace(System.err);
|
||||
} finally {
|
||||
fetching.set(false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,20 +2,21 @@ package com.andrewlalis.perfin.control.component;
|
|||
|
||||
import com.andrewlalis.perfin.data.CurrencyUtil;
|
||||
import com.andrewlalis.perfin.data.DateUtil;
|
||||
import com.andrewlalis.perfin.model.*;
|
||||
import com.andrewlalis.perfin.model.CreditAndDebitAccounts;
|
||||
import com.andrewlalis.perfin.model.Profile;
|
||||
import com.andrewlalis.perfin.model.Transaction;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import javafx.util.Pair;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
|
@ -25,21 +26,32 @@ import static com.andrewlalis.perfin.PerfinApp.router;
|
|||
* A tile that displays a transaction's basic information.
|
||||
*/
|
||||
public class TransactionTile extends BorderPane {
|
||||
public TransactionTile(Transaction transaction, Runnable refresh) {
|
||||
setStyle("""
|
||||
public final BooleanProperty selected = new SimpleBooleanProperty(false);
|
||||
private static final String UNSELECTED_STYLE = """
|
||||
-fx-border-color: lightgray;
|
||||
-fx-border-width: 1px;
|
||||
-fx-border-style: solid;
|
||||
-fx-border-radius: 5px;
|
||||
-fx-padding: 5px;
|
||||
-fx-max-width: 500px;
|
||||
-fx-cursor: hand;
|
||||
""");
|
||||
""";
|
||||
private static final String SELECTED_STYLE = """
|
||||
-fx-border-color: white;
|
||||
-fx-border-width: 1px;
|
||||
-fx-border-style: solid;
|
||||
-fx-border-radius: 5px;
|
||||
-fx-padding: 5px;
|
||||
-fx-cursor: hand;
|
||||
""";
|
||||
|
||||
public TransactionTile(Transaction transaction) {
|
||||
setStyle(UNSELECTED_STYLE);
|
||||
|
||||
setTop(getHeader(transaction));
|
||||
setCenter(getBody(transaction));
|
||||
setBottom(getFooter(transaction, refresh));
|
||||
addEventHandler(MouseEvent.MOUSE_CLICKED, event -> router.navigate("transaction", transaction));
|
||||
setBottom(getFooter(transaction));
|
||||
|
||||
styleProperty().bind(selected.map(value -> value ? SELECTED_STYLE : UNSELECTED_STYLE));
|
||||
}
|
||||
|
||||
private Node getHeader(Transaction transaction) {
|
||||
|
@ -64,31 +76,23 @@ public class TransactionTile extends BorderPane {
|
|||
accounts.ifCredit(acc -> {
|
||||
Hyperlink link = new Hyperlink(acc.getShortName());
|
||||
link.setOnAction(event -> router.navigate("account", acc));
|
||||
TextFlow text = new TextFlow(new Text("Credited from"), link);
|
||||
Platform.runLater(() -> bodyVBox.getChildren().add(text));
|
||||
Text prefix = new Text("Credited from");
|
||||
prefix.setFill(Color.RED);
|
||||
Platform.runLater(() -> bodyVBox.getChildren().add(new TextFlow(prefix, link)));
|
||||
});
|
||||
accounts.ifDebit(acc -> {
|
||||
Hyperlink link = new Hyperlink(acc.getShortName());
|
||||
link.setOnAction(event -> router.navigate("account", acc));
|
||||
TextFlow text = new TextFlow(new Text("Debited to"), link);
|
||||
Platform.runLater(() -> bodyVBox.getChildren().add(text));
|
||||
Text prefix = new Text("Debited to");
|
||||
prefix.setFill(Color.GREEN);
|
||||
Platform.runLater(() -> bodyVBox.getChildren().add(new TextFlow(prefix, link)));
|
||||
});
|
||||
});
|
||||
return bodyVBox;
|
||||
}
|
||||
|
||||
private Node getFooter(Transaction transaction, Runnable refresh) {
|
||||
private Node getFooter(Transaction transaction) {
|
||||
Label timestampLabel = new Label(DateUtil.formatUTCAsLocalWithZone(transaction.getTimestamp()));
|
||||
Hyperlink deleteLink = new Hyperlink("Delete this transaction");
|
||||
deleteLink.setOnAction(event -> {
|
||||
var confirmResult = new Alert(Alert.AlertType.CONFIRMATION, "Are you sure you want to delete this transaction?").showAndWait();
|
||||
if (confirmResult.isPresent() && confirmResult.get() == ButtonType.OK) {
|
||||
Profile.getCurrent().getDataSource().useTransactionRepository(repo -> {
|
||||
repo.delete(transaction.getId());
|
||||
});
|
||||
refresh.run();
|
||||
}
|
||||
});
|
||||
HBox footerHBox = new HBox(
|
||||
timestampLabel
|
||||
);
|
||||
|
|
|
@ -12,6 +12,7 @@ public interface TransactionRepository extends AutoCloseable {
|
|||
long insert(Transaction transaction, Map<Long, AccountEntry.Type> accountsMap);
|
||||
void addAttachments(long transactionId, List<TransactionAttachment> attachments);
|
||||
Page<Transaction> findAll(PageRequest pagination);
|
||||
long countAll();
|
||||
Page<Transaction> findAllByAccounts(Set<Long> accountIds, PageRequest pagination);
|
||||
Map<AccountEntry, Account> findEntriesWithAccounts(long transactionId);
|
||||
CreditAndDebitAccounts findLinkedAccounts(long transactionId);
|
||||
|
|
|
@ -91,6 +91,11 @@ public record JdbcTransactionRepository(Connection conn) implements TransactionR
|
|||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countAll() {
|
||||
return DbUtil.findOne(conn, "SELECT COUNT(id) FROM transaction", Collections.emptyList(), rs -> rs.getLong(1)).orElse(0L);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Transaction> findAllByAccounts(Set<Long> accountIds, PageRequest pagination) {
|
||||
String idsStr = accountIds.stream().map(String::valueOf).collect(Collectors.joining(","));
|
||||
|
|
|
@ -1,5 +1,15 @@
|
|||
package com.andrewlalis.perfin.data.pagination;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public record Page<T>(List<T> items, PageRequest pagination) {}
|
||||
public record Page<T>(List<T> items, PageRequest pagination) {
|
||||
public Stream<T> stream() {
|
||||
return items.stream();
|
||||
}
|
||||
|
||||
public <U> Page<U> map(Function<T, U> mapper) {
|
||||
return new Page<>(items.stream().map(mapper).toList(), pagination);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,3 +26,24 @@
|
|||
.std-spacing {
|
||||
-fx-spacing: 3px;
|
||||
}
|
||||
|
||||
.spacing-extra {
|
||||
-fx-spacing: 6px;
|
||||
}
|
||||
|
||||
/* DEBUG BORDERS */
|
||||
.debug-border-1 {
|
||||
-fx-border-color: red;
|
||||
}
|
||||
|
||||
.debug-border-2 {
|
||||
-fx-border-color: lime;
|
||||
}
|
||||
|
||||
.debug-border-3 {
|
||||
-fx-border-color: blue;
|
||||
}
|
||||
|
||||
.debug-border-4 {
|
||||
-fx-border-color: magenta;
|
||||
}
|
||||
|
|
|
@ -8,12 +8,8 @@
|
|||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="com.andrewlalis.perfin.control.TransactionViewController"
|
||||
>
|
||||
<top>
|
||||
<HBox styleClass="std-padding,std-spacing">
|
||||
<Label text="Transaction" styleClass="large-text,bold-text"/>
|
||||
</HBox>
|
||||
</top>
|
||||
<center>
|
||||
<ScrollPane fitToHeight="true" fitToWidth="true">
|
||||
<VBox styleClass="std-padding,std-spacing">
|
||||
<VBox>
|
||||
<Label text="Amount" styleClass="bold-text"/>
|
||||
|
@ -25,7 +21,7 @@
|
|||
</VBox>
|
||||
<VBox>
|
||||
<Label text="Description" styleClass="bold-text"/>
|
||||
<Label fx:id="descriptionLabel" wrapText="true"/>
|
||||
<Label fx:id="descriptionLabel" wrapText="true" style="-fx-min-height: 100px;" alignment="TOP_LEFT"/>
|
||||
</VBox>
|
||||
<Separator/>
|
||||
<VBox>
|
||||
|
@ -45,12 +41,11 @@
|
|||
<HBox fx:id="attachmentsHBox" styleClass="std-padding,std-spacing"/>
|
||||
</ScrollPane>
|
||||
</VBox>
|
||||
</VBox>
|
||||
</center>
|
||||
<right>
|
||||
<VBox styleClass="std-padding,std-spacing">
|
||||
<Label text="Actions" styleClass="bold-text"/>
|
||||
<Separator/>
|
||||
<FlowPane styleClass="std-padding, std-spacing">
|
||||
<Button text="Delete" onAction="#deleteTransaction"/>
|
||||
</FlowPane>
|
||||
</VBox>
|
||||
</right>
|
||||
</ScrollPane>
|
||||
</center>
|
||||
</BorderPane>
|
||||
|
|
|
@ -1,24 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.BorderPane?>
|
||||
<?import javafx.scene.control.ScrollPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.control.ScrollPane?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<BorderPane xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="com.andrewlalis.perfin.control.TransactionsViewController"
|
||||
>
|
||||
<top>
|
||||
<HBox style="-fx-padding: 3px;">
|
||||
<HBox styleClass="std-padding,std-spacing">
|
||||
<Button text="Add Transaction" onAction="#addTransaction"/>
|
||||
</HBox>
|
||||
</top>
|
||||
<center>
|
||||
<Label text="Center"/>
|
||||
<HBox>
|
||||
<BorderPane fx:id="transactionsListBorderPane" HBox.hgrow="ALWAYS">
|
||||
<center>
|
||||
<ScrollPane fitToHeight="true" fitToWidth="true">
|
||||
<VBox fx:id="transactionsVBox" style="-fx-padding: 3px; -fx-spacing: 5px;"/>
|
||||
<VBox fx:id="transactionsVBox" styleClass="std-padding,spacing-extra"/>
|
||||
</ScrollPane>
|
||||
</center>
|
||||
</BorderPane>
|
||||
<VBox fx:id="detailPanel"/>
|
||||
</HBox>
|
||||
</center>
|
||||
</BorderPane>
|
||||
|
|
Loading…
Reference in New Issue