Add Transaction Properties #15
|
@ -15,13 +15,14 @@ import com.andrewlalis.perfin.view.component.validation.ValidationApplier;
|
||||||
import com.andrewlalis.perfin.view.component.validation.validators.CurrencyAmountValidator;
|
import com.andrewlalis.perfin.view.component.validation.validators.CurrencyAmountValidator;
|
||||||
import com.andrewlalis.perfin.view.component.validation.validators.PredicateValidator;
|
import com.andrewlalis.perfin.view.component.validation.validators.PredicateValidator;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.binding.BooleanExpression;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.event.ActionEvent;
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.input.KeyCode;
|
import javafx.scene.input.KeyCode;
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
|
@ -39,6 +40,10 @@ import java.util.*;
|
||||||
|
|
||||||
import static com.andrewlalis.perfin.PerfinApp.router;
|
import static com.andrewlalis.perfin.PerfinApp.router;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller for the "edit-transaction" view, which is where the user can
|
||||||
|
* create or edit transactions.
|
||||||
|
*/
|
||||||
public class EditTransactionController implements RouteSelectionListener {
|
public class EditTransactionController implements RouteSelectionListener {
|
||||||
private static final Logger log = LoggerFactory.getLogger(EditTransactionController.class);
|
private static final Logger log = LoggerFactory.getLogger(EditTransactionController.class);
|
||||||
|
|
||||||
|
@ -82,45 +87,8 @@ public class EditTransactionController implements RouteSelectionListener {
|
||||||
var descriptionValid = new ValidationApplier<>(new PredicateValidator<String>()
|
var descriptionValid = new ValidationApplier<>(new PredicateValidator<String>()
|
||||||
.addTerminalPredicate(s -> s == null || s.length() <= 255, "Description is too long.")
|
.addTerminalPredicate(s -> s == null || s.length() <= 255, "Description is too long.")
|
||||||
).validatedInitially().attach(descriptionField, descriptionField.textProperty());
|
).validatedInitially().attach(descriptionField, descriptionField.textProperty());
|
||||||
|
var linkedAccountsValid = initializeLinkedAccountsValidationUi();
|
||||||
// Linked accounts will use a property derived from both the debit and credit selections.
|
initializeTagSelectionUi();
|
||||||
Property<CreditAndDebitAccounts> linkedAccountsProperty = new SimpleObjectProperty<>(getSelectedAccounts());
|
|
||||||
debitAccountSelector.valueProperty().addListener((observable, oldValue, newValue) -> linkedAccountsProperty.setValue(getSelectedAccounts()));
|
|
||||||
creditAccountSelector.valueProperty().addListener((observable, oldValue, newValue) -> linkedAccountsProperty.setValue(getSelectedAccounts()));
|
|
||||||
var linkedAccountsValid = new ValidationApplier<>(getLinkedAccountsValidator())
|
|
||||||
.validatedInitially()
|
|
||||||
.attach(linkedAccountsContainer, linkedAccountsProperty);
|
|
||||||
|
|
||||||
// Set up the list of added tags.
|
|
||||||
addTagButton.disableProperty().bind(tagsComboBox.valueProperty().map(s -> s == null || s.isBlank()));
|
|
||||||
addTagButton.setOnAction(event -> {
|
|
||||||
if (tagsComboBox.getValue() == null) return;
|
|
||||||
String tag = tagsComboBox.getValue().strip();
|
|
||||||
if (!selectedTags.contains(tag)) {
|
|
||||||
selectedTags.add(tag);
|
|
||||||
selectedTags.sort(String::compareToIgnoreCase);
|
|
||||||
}
|
|
||||||
tagsComboBox.setValue(null);
|
|
||||||
});
|
|
||||||
tagsComboBox.setOnKeyPressed(event -> {
|
|
||||||
if (event.getCode() == KeyCode.ENTER) {
|
|
||||||
addTagButton.fire();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
BindingUtil.mapContent(tagsVBox.getChildren(), selectedTags, tag -> {
|
|
||||||
Label label = new Label(tag);
|
|
||||||
label.setMaxWidth(Double.POSITIVE_INFINITY);
|
|
||||||
label.getStyleClass().addAll("bold-text");
|
|
||||||
Button removeButton = new Button("Remove");
|
|
||||||
removeButton.setOnAction(event -> {
|
|
||||||
selectedTags.remove(tag);
|
|
||||||
});
|
|
||||||
BorderPane tile = new BorderPane(label);
|
|
||||||
tile.setRight(removeButton);
|
|
||||||
tile.getStyleClass().addAll("std-spacing");
|
|
||||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
|
||||||
return tile;
|
|
||||||
});
|
|
||||||
|
|
||||||
var formValid = timestampValid.and(amountValid).and(descriptionValid).and(linkedAccountsValid);
|
var formValid = timestampValid.and(amountValid).and(descriptionValid).and(linkedAccountsValid);
|
||||||
saveButton.disableProperty().bind(formValid.not());
|
saveButton.disableProperty().bind(formValid.not());
|
||||||
|
@ -279,6 +247,47 @@ public class EditTransactionController implements RouteSelectionListener {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BooleanExpression initializeLinkedAccountsValidationUi() {
|
||||||
|
Property<CreditAndDebitAccounts> linkedAccountsProperty = new SimpleObjectProperty<>(getSelectedAccounts());
|
||||||
|
debitAccountSelector.valueProperty().addListener((observable, oldValue, newValue) -> linkedAccountsProperty.setValue(getSelectedAccounts()));
|
||||||
|
creditAccountSelector.valueProperty().addListener((observable, oldValue, newValue) -> linkedAccountsProperty.setValue(getSelectedAccounts()));
|
||||||
|
return new ValidationApplier<>(getLinkedAccountsValidator())
|
||||||
|
.validatedInitially()
|
||||||
|
.attach(linkedAccountsContainer, linkedAccountsProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeTagSelectionUi() {
|
||||||
|
addTagButton.disableProperty().bind(tagsComboBox.valueProperty().map(s -> s == null || s.isBlank()));
|
||||||
|
addTagButton.setOnAction(event -> {
|
||||||
|
if (tagsComboBox.getValue() == null) return;
|
||||||
|
String tag = tagsComboBox.getValue().strip();
|
||||||
|
if (!selectedTags.contains(tag)) {
|
||||||
|
selectedTags.add(tag);
|
||||||
|
selectedTags.sort(String::compareToIgnoreCase);
|
||||||
|
}
|
||||||
|
tagsComboBox.setValue(null);
|
||||||
|
});
|
||||||
|
tagsComboBox.setOnKeyPressed(event -> {
|
||||||
|
if (event.getCode() == KeyCode.ENTER) {
|
||||||
|
addTagButton.fire();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
BindingUtil.mapContent(tagsVBox.getChildren(), selectedTags, this::createTagListTile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node createTagListTile(String tag) {
|
||||||
|
Label label = new Label(tag);
|
||||||
|
label.setMaxWidth(Double.POSITIVE_INFINITY);
|
||||||
|
label.getStyleClass().addAll("bold-text");
|
||||||
|
Button removeButton = new Button("Remove");
|
||||||
|
removeButton.setOnAction(event -> selectedTags.remove(tag));
|
||||||
|
BorderPane tile = new BorderPane(label);
|
||||||
|
tile.setRight(removeButton);
|
||||||
|
tile.getStyleClass().addAll("std-spacing");
|
||||||
|
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||||
|
return tile;
|
||||||
|
}
|
||||||
|
|
||||||
private CreditAndDebitAccounts getSelectedAccounts() {
|
private CreditAndDebitAccounts getSelectedAccounts() {
|
||||||
return new CreditAndDebitAccounts(
|
return new CreditAndDebitAccounts(
|
||||||
creditAccountSelector.getValue(),
|
creditAccountSelector.getValue(),
|
||||||
|
|
|
@ -60,13 +60,22 @@
|
||||||
<ColumnConstraints hgrow="ALWAYS" halignment="RIGHT"/>
|
<ColumnConstraints hgrow="ALWAYS" halignment="RIGHT"/>
|
||||||
</columnConstraints>
|
</columnConstraints>
|
||||||
|
|
||||||
|
<VBox>
|
||||||
<Label text="Vendor" labelFor="${vendorComboBox}" styleClass="bold-text"/>
|
<Label text="Vendor" labelFor="${vendorComboBox}" styleClass="bold-text"/>
|
||||||
|
<Hyperlink text="Manage vendors" styleClass="small-font"/>
|
||||||
|
</VBox>
|
||||||
<ComboBox fx:id="vendorComboBox" editable="true" maxWidth="Infinity"/>
|
<ComboBox fx:id="vendorComboBox" editable="true" maxWidth="Infinity"/>
|
||||||
|
|
||||||
|
<VBox>
|
||||||
<Label text="Category" labelFor="${categoryComboBox}" styleClass="bold-text"/>
|
<Label text="Category" labelFor="${categoryComboBox}" styleClass="bold-text"/>
|
||||||
|
<Hyperlink text="Manage categories" styleClass="small-font"/>
|
||||||
|
</VBox>
|
||||||
<ComboBox fx:id="categoryComboBox" editable="true" maxWidth="Infinity"/>
|
<ComboBox fx:id="categoryComboBox" editable="true" maxWidth="Infinity"/>
|
||||||
|
|
||||||
|
<VBox>
|
||||||
<Label text="Tags" labelFor="${tagsComboBox}" styleClass="bold-text"/>
|
<Label text="Tags" labelFor="${tagsComboBox}" styleClass="bold-text"/>
|
||||||
|
<Hyperlink text="Manage tags" styleClass="small-font"/>
|
||||||
|
</VBox>
|
||||||
<VBox maxWidth="Infinity">
|
<VBox maxWidth="Infinity">
|
||||||
<HBox styleClass="std-spacing">
|
<HBox styleClass="std-spacing">
|
||||||
<ComboBox fx:id="tagsComboBox" editable="true" HBox.hgrow="ALWAYS" maxWidth="Infinity"/>
|
<ComboBox fx:id="tagsComboBox" editable="true" HBox.hgrow="ALWAYS" maxWidth="Infinity"/>
|
||||||
|
|
Loading…
Reference in New Issue