validation rules are now editable through context menu

This commit is contained in:
Andreas Billmann
2016-02-05 08:00:32 +01:00
parent 10c2592510
commit fc26dcc9aa
14 changed files with 787 additions and 101 deletions

View File

@@ -30,6 +30,8 @@ import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ListChangeListener;
import javafx.collections.WeakListChangeListener;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
@@ -53,10 +55,12 @@ import ninja.javafx.smartcsv.fx.table.model.CSVRow;
import ninja.javafx.smartcsv.fx.table.model.CSVValue;
import ninja.javafx.smartcsv.fx.util.LoadFileService;
import ninja.javafx.smartcsv.fx.util.SaveFileService;
import ninja.javafx.smartcsv.fx.validation.ValidationEditorController;
import ninja.javafx.smartcsv.preferences.PreferencesFileReader;
import ninja.javafx.smartcsv.preferences.PreferencesFileWriter;
import ninja.javafx.smartcsv.validation.ValidationError;
import ninja.javafx.smartcsv.validation.ValidationFileReader;
import ninja.javafx.smartcsv.validation.ValidationFileWriter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@@ -69,6 +73,7 @@ import java.util.Optional;
import java.util.ResourceBundle;
import static java.lang.Math.max;
import static java.text.MessageFormat.format;
import static javafx.application.Platform.exit;
import static javafx.application.Platform.runLater;
import static javafx.beans.binding.Bindings.*;
@@ -89,6 +94,10 @@ public class SmartCSVController extends FXMLController {
".SmartCSV.fx" +
File.separator + "" +
"preferences.json");
public static final String CSV_FILTER_TEXT = "CSV files (*.csv)";
public static final String CSV_FILTER_EXTENSION = "*.csv";
public static final String JSON_FILTER_TEXT = "JSON files (*.json)";
public static final String JSON_FILTER_EXTENSION = "*.json";
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// injections
@@ -109,12 +118,18 @@ public class SmartCSVController extends FXMLController {
@Autowired
private CSVFileWriter csvFileWriter;
@Autowired
private ValidationFileWriter validationFileWriter;
@Autowired
private AboutController aboutController;
@Autowired
private PreferencesController preferencesController;
@Autowired
private ValidationEditorController validationEditorController;
@Autowired
private LoadFileService loadFileService;
@@ -142,6 +157,15 @@ public class SmartCSVController extends FXMLController {
@FXML
private MenuItem saveAsMenuItem;
@FXML
private MenuItem loadConfigMenuItem;
@FXML
private MenuItem saveConfigMenuItem;
@FXML
private MenuItem saveAsConfigMenuItem;
@FXML
private MenuItem deleteRowMenuItem;
@@ -154,14 +178,21 @@ public class SmartCSVController extends FXMLController {
@FXML
private Button saveAsButton;
@FXML
private Button loadConfigButton;
@FXML
private Button saveConfigButton;
@FXML
private Button saveAsConfigButton;
@FXML
private Button deleteRowButton;
@FXML
private Button addRowButton;
private ErrorSideBar errorSideBar;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// members
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -170,11 +201,14 @@ public class SmartCSVController extends FXMLController {
private CSVModel model;
private TableView<CSVRow> tableView;
private ErrorSideBar errorSideBar;
private BooleanProperty fileChanged = new SimpleBooleanProperty(true);
private ResourceBundle resourceBundle;
private ObjectProperty<File> currentCsvFile = new SimpleObjectProperty<>();
private ObjectProperty<File> currentConfigFile= new SimpleObjectProperty<>();
private ListChangeListener<ValidationError> errorListListener = c -> tableView.refresh();
private WeakListChangeListener<ValidationError> weakErrorListListener = new WeakListChangeListener<>(errorListListener);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// init
@@ -185,22 +219,28 @@ public class SmartCSVController extends FXMLController {
this.resourceBundle = resourceBundle;
setupTableCellFactory();
setupErrorSideBar(resourceBundle);
errorSideBar = new ErrorSideBar(resourceBundle);
bindMenuItemsToFileExistence(currentCsvFile, saveMenuItem, saveAsMenuItem, addRowMenuItem, loadConfigMenuItem);
bindButtonsToFileExistence(currentCsvFile, saveButton, saveAsButton, addRowButton, loadConfigButton);
errorSideBar.selectedValidationErrorProperty().addListener((observable, oldValue, newValue) -> {
scrollToError(newValue);
});
applicationPane.setRight(errorSideBar);
bindMenuItemsToFileExistence(currentConfigFile, saveConfigMenuItem, saveAsConfigMenuItem);
bindButtonsToFileExistence(currentConfigFile, saveAsConfigButton, saveConfigButton);
bindMenuItemsToCsvFileExtistence(saveMenuItem, saveAsMenuItem, addRowMenuItem);
bindButtonsToCsvFileExistence(saveButton, saveAsButton, addRowButton);
bindCsvFileName();
bindConfigFileName();
loadCsvPreferencesFromFile();
}
private void setupErrorSideBar(ResourceBundle resourceBundle) {
errorSideBar = new ErrorSideBar(resourceBundle);
errorSideBar.selectedValidationErrorProperty().addListener((observable, oldValue, newValue) -> {
scrollToError(newValue);
});
applicationPane.setRight(errorSideBar);
}
private void setupTableCellFactory() {
cellFactory = new ValidationCellFactory(resourceBundle);
}
@@ -226,8 +266,8 @@ public class SmartCSVController extends FXMLController {
currentCsvFile.setValue(
loadFile(
csvLoader,
"CSV files (*.csv)",
"*.csv",
CSV_FILTER_TEXT,
CSV_FILTER_EXTENSION,
"Open CSV",
currentCsvFile.getValue()));
}
@@ -237,8 +277,8 @@ public class SmartCSVController extends FXMLController {
currentConfigFile.setValue(
loadFile(
validationLoader,
"JSON files (*.json)",
"*.json",
JSON_FILTER_TEXT,
JSON_FILTER_EXTENSION,
"Open Validation Configuration",
currentConfigFile.getValue()));
}
@@ -255,11 +295,28 @@ public class SmartCSVController extends FXMLController {
currentCsvFile.setValue(
saveFile(
csvFileWriter,
"CSV files (*.csv)",
"*.csv",
CSV_FILTER_TEXT,
CSV_FILTER_EXTENSION,
currentCsvFile.getValue()));
}
@FXML
public void saveConfig(ActionEvent actionEvent) {
validationFileWriter.setValidationConfiguration(validationLoader.getValidationConfiguration());
useSaveFileService(validationFileWriter, currentConfigFile.getValue());
}
@FXML
public void saveAsConfig(ActionEvent actionEvent) {
validationFileWriter.setValidationConfiguration(validationLoader.getValidationConfiguration());
currentConfigFile.setValue(
saveFile(
validationFileWriter,
JSON_FILTER_TEXT,
JSON_FILTER_EXTENSION,
currentConfigFile.getValue()));
}
@FXML
public void close(ActionEvent actionEvent) {
if (canExit()) {
@@ -334,6 +391,25 @@ public class SmartCSVController extends FXMLController {
return canExit;
}
public void showValidationEditor(String column) {
validationEditorController.setSelectedColumn(column);
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setGraphic(null);
alert.setTitle(resourceBundle.getString("dialog.validation.rules.title"));
alert.setHeaderText(format(resourceBundle.getString("dialog.validation.rules.header"), column));
alert.getDialogPane().setContent(validationEditorController.getView());
Optional<ButtonType> result = alert.showAndWait();
if (result.get() == ButtonType.OK){
runLater(() -> {
validationEditorController.updateConfiguration();
fileChanged.setValue(true);
model.revalidate();
});
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// private methods
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -345,15 +421,15 @@ public class SmartCSVController extends FXMLController {
tableView.getSelectionModel().select(lastRow);
}
private void bindMenuItemsToCsvFileExtistence(MenuItem... items) {
private void bindMenuItemsToFileExistence(ObjectProperty<File> file, MenuItem... items) {
for (MenuItem item: items) {
item.disableProperty().bind(isNull(currentCsvFile));
item.disableProperty().bind(isNull(file));
}
}
private void bindButtonsToCsvFileExistence(Button... items) {
private void bindButtonsToFileExistence(ObjectProperty<File> file, Button... items) {
for (Button item: items) {
item.disableProperty().bind(isNull(currentCsvFile));
item.disableProperty().bind(isNull(file));
}
}
@@ -489,7 +565,9 @@ public class SmartCSVController extends FXMLController {
private void resetContent() {
model = csvLoader.getData();
if (model != null) {
model.setValidator(validationLoader.getValidator());
model.getValidationError().addListener(weakErrorListListener);
model.setValidationConfiguration(validationLoader.getValidationConfiguration());
validationEditorController.setValidationConfiguration(validationLoader.getValidationConfiguration());
tableView = new TableView<>();
bindMenuItemsToTableSelection(deleteRowMenuItem);
@@ -521,6 +599,11 @@ public class SmartCSVController extends FXMLController {
column.setCellValueFactory(new ObservableMapValueFactory(header));
column.setCellFactory(cellFactory);
column.setEditable(true);
column.setSortable(false);
ContextMenu contextMenu = contextMenuForColumn(header);
column.setContextMenu(contextMenu);
column.setOnEditCommit(new EventHandler<TableColumn.CellEditEvent<CSVRow, CSVValue>>() {
@Override
public void handle(TableColumn.CellEditEvent<CSVRow, CSVValue> event) {
@@ -536,6 +619,15 @@ public class SmartCSVController extends FXMLController {
tableView.getColumns().add(column);
}
private ContextMenu contextMenuForColumn(String header) {
ContextMenu contextMenu = new ContextMenu();
MenuItem editColumnRulesMenuItem = new MenuItem(resourceBundle.getString("context.menu.edit.column.rules"));
bindMenuItemsToFileExistence(currentConfigFile, editColumnRulesMenuItem);
editColumnRulesMenuItem.setOnAction(e -> showValidationEditor(header));
contextMenu.getItems().addAll(editColumnRulesMenuItem);
return contextMenu;
}
private void scrollToError(ValidationError entry) {
if (entry != null) {
if (entry.getLineNumber() != null) {

View File

@@ -26,12 +26,8 @@
package ninja.javafx.smartcsv.fx.preferences;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;

View File

@@ -28,6 +28,7 @@ package ninja.javafx.smartcsv.fx.table.model;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import ninja.javafx.smartcsv.validation.ValidationConfiguration;
import ninja.javafx.smartcsv.validation.ValidationError;
import ninja.javafx.smartcsv.validation.Validator;
@@ -43,11 +44,11 @@ public class CSVModel {
private ObservableList<ValidationError> validationError = FXCollections.observableArrayList();
/**
* sets the validator for the data revalidates
* @param validator the validator for this data
* sets the validator configuration for the data revalidates
* @param validationConfiguration the validator configuration for this data
*/
public void setValidator(Validator validator) {
this.validator = validator;
public void setValidationConfiguration(ValidationConfiguration validationConfiguration) {
this.validator = new Validator(validationConfiguration);
revalidate();
}

View File

@@ -0,0 +1,348 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.fx.validation;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import ninja.javafx.smartcsv.fx.FXMLController;
import ninja.javafx.smartcsv.validation.ValidationConfiguration;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.net.URL;
import java.util.List;
import java.util.ResourceBundle;
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.joining;
/**
* controller for editing column validations
*/
@Component
public class ValidationEditorController extends FXMLController {
private StringProperty selectedColumn = new SimpleStringProperty();
private ValidationConfiguration validationConfiguration;
@FXML
private CheckBox notEmptyRuleCheckBox;
@FXML
private CheckBox integerRuleCheckBox;
@FXML
private CheckBox doublerRuleCheckBox;
@FXML
private CheckBox alphanumericRuleCheckBox;
@FXML
private Spinner<Integer> minLengthSpinner;
@FXML
private Spinner<Integer> maxLengthSpinner;
@FXML
private TextField dateformatRuleTextField;
@FXML
private TextField regexpRuleTextField;
@FXML
private TextField valueOfRuleTextField;
@FXML
private TextArea groovyRuleTextArea;
@FXML
private CheckBox enableNotEmptyRule;
@FXML
private CheckBox enableIntegerRule;
@FXML
private CheckBox enableDoubleRule;
@FXML
private CheckBox enableAlphanumericRule;
@FXML
private CheckBox enableMinLengthRule;
@FXML
private CheckBox enableMaxLengthRule;
@FXML
private CheckBox enableDateRule;
@FXML
private CheckBox enableRegexpRule;
@FXML
private CheckBox enableValueOfRule;
@FXML
private CheckBox enableGroovyRule;
@Value("${fxml.smartcvs.validation.editor.view}")
@Override
public void setFxmlFilePath(String filePath) {
this.fxmlFilePath = filePath;
}
@Override
public void initialize(URL location, ResourceBundle resources) {
minLengthSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, Integer.MAX_VALUE, 0));
maxLengthSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, Integer.MAX_VALUE, 0));
initCheckBox(notEmptyRuleCheckBox, enableNotEmptyRule);
initCheckBox(integerRuleCheckBox, enableIntegerRule);
initCheckBox(doublerRuleCheckBox, enableDoubleRule);
initCheckBox(alphanumericRuleCheckBox, enableAlphanumericRule);
initSpinner(minLengthSpinner, enableMinLengthRule);
initSpinner(maxLengthSpinner, enableMaxLengthRule);
initTextInputControl(dateformatRuleTextField, enableDateRule);
initTextInputControl(regexpRuleTextField, enableRegexpRule);
initTextInputControl(valueOfRuleTextField, enableValueOfRule);
initTextInputControl(groovyRuleTextArea, enableGroovyRule);
selectedColumn.addListener(observable -> {
updateForm();
});
}
public String getSelectedColumn() {
return selectedColumn.get();
}
public StringProperty selectedColumnProperty() {
return selectedColumn;
}
public void setSelectedColumn(String selectedColumn) {
this.selectedColumn.set(selectedColumn);
}
public void setValidationConfiguration(ValidationConfiguration validationConfiguration) {
this.validationConfiguration = validationConfiguration;
}
public void updateConfiguration() {
if (enableIntegerRule.isSelected()) {
validationConfiguration.setIntegerRuleFor(selectedColumn.getValue(), integerRuleCheckBox.isSelected());
} else {
validationConfiguration.setIntegerRuleFor(selectedColumn.getValue(), null);
}
if (enableNotEmptyRule.isSelected()) {
validationConfiguration.setNotEmptyRuleFor(selectedColumn.getValue(), notEmptyRuleCheckBox.isSelected());
} else {
validationConfiguration.setNotEmptyRuleFor(selectedColumn.getValue(), null);
}
if (enableDoubleRule.isSelected()) {
validationConfiguration.setDoubleRuleFor(selectedColumn.getValue(), doublerRuleCheckBox.isSelected());
} else {
validationConfiguration.setDoubleRuleFor(selectedColumn.getValue(), null);
}
if (enableAlphanumericRule.isSelected()) {
validationConfiguration.setAlphanumericRuleFor(selectedColumn.getValue(), alphanumericRuleCheckBox.isSelected());
} else {
validationConfiguration.setAlphanumericRuleFor(selectedColumn.getValue(), null);
}
if (enableDateRule.isSelected()) {
validationConfiguration.setDateRuleFor(selectedColumn.getValue(), dateformatRuleTextField.getText());
} else {
validationConfiguration.setDateRuleFor(selectedColumn.getValue(), null);
}
if (enableGroovyRule.isSelected()) {
validationConfiguration.setGroovyRuleFor(selectedColumn.getValue(), groovyRuleTextArea.getText());
} else {
validationConfiguration.setGroovyRuleFor(selectedColumn.getValue(), null);
}
if (enableMinLengthRule.isSelected()) {
validationConfiguration.setMinLengthRuleFor(selectedColumn.getValue(), minLengthSpinner.getValue());
} else {
validationConfiguration.setMinLengthRuleFor(selectedColumn.getValue(), null);
}
if (enableMaxLengthRule.isSelected()) {
validationConfiguration.setMaxLengthRuleFor(selectedColumn.getValue(), maxLengthSpinner.getValue());
} else {
validationConfiguration.setMaxLengthRuleFor(selectedColumn.getValue(), null);
}
if (enableRegexpRule.isSelected()) {
validationConfiguration.setRegexpRuleFor(selectedColumn.getValue(), regexpRuleTextField.getText());
} else {
validationConfiguration.setRegexpRuleFor(selectedColumn.getValue(), null);
}
if (enableValueOfRule.isSelected()) {
validationConfiguration.setValueOfRuleFor(selectedColumn.getValue(), asList(valueOfRuleTextField.getText().split(", ")));
} else {
validationConfiguration.setValueOfRuleFor(selectedColumn.getValue(), null);
}
}
private void updateForm() {
updateCheckBox(
notEmptyRuleCheckBox,
validationConfiguration.getNotEmptyRuleFor(getSelectedColumn()),
enableNotEmptyRule
);
updateCheckBox(
integerRuleCheckBox,
validationConfiguration.getIntegerRuleFor(getSelectedColumn()),
enableIntegerRule
);
updateCheckBox(
doublerRuleCheckBox,
validationConfiguration.getDoubleRuleFor(getSelectedColumn()),
enableDoubleRule
);
updateCheckBox(
alphanumericRuleCheckBox,
validationConfiguration.getAlphanumericRuleFor(getSelectedColumn()),
enableAlphanumericRule
);
updateSpinner(
minLengthSpinner,
validationConfiguration.getMinLengthRuleFor(getSelectedColumn()),
enableMinLengthRule
);
updateSpinner(
maxLengthSpinner,
validationConfiguration.getMaxLengthRuleFor(getSelectedColumn()),
enableMaxLengthRule
);
updateTextInputControl(
dateformatRuleTextField,
validationConfiguration.getDateRuleFor(getSelectedColumn()),
enableDateRule
);
updateTextInputControl(
regexpRuleTextField,
validationConfiguration.getRegexpRuleFor(getSelectedColumn()),
enableRegexpRule
);
updateTextInputControl(
valueOfRuleTextField,
validationConfiguration.getValueOfRuleFor(getSelectedColumn()),
enableValueOfRule
);
updateTextInputControl(
groovyRuleTextArea,
validationConfiguration.getGroovyRuleFor(getSelectedColumn()),
enableGroovyRule
);
}
private void updateCheckBox(CheckBox rule, Boolean value, CheckBox ruleEnabled) {
if (value == null) {
ruleEnabled.setSelected(false);
} else {
rule.setSelected(value);
ruleEnabled.setSelected(true);
}
}
private void updateSpinner(Spinner rule, Integer value, CheckBox ruleEnabled) {
if (value == null) {
ruleEnabled.setSelected(false);
} else {
ruleEnabled.setSelected(true);
rule.getValueFactory().setValue(value);
}
}
private void updateTextInputControl(TextInputControl rule, String value, CheckBox ruleEnabled) {
if (value == null) {
ruleEnabled.setSelected(false);
} else {
ruleEnabled.setSelected(true);
rule.setText(value);
}
}
private void updateTextInputControl(TextInputControl rule, List<String> values, CheckBox ruleEnabled) {
if (values == null || values.isEmpty()) {
ruleEnabled.setSelected(false);
} else {
ruleEnabled.setSelected(true);
rule.setText(values.stream().collect(joining(", ")));
}
}
private void initCheckBox(CheckBox rule, CheckBox ruleEnabled) {
rule.disableProperty().bind(ruleEnabled.selectedProperty().not());
ruleEnabled.selectedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
rule.setSelected(false);
}
});
}
private void initSpinner(Spinner rule, CheckBox ruleEnabled) {
rule.disableProperty().bind(ruleEnabled.selectedProperty().not());
ruleEnabled.selectedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
rule.getValueFactory().setValue(0);
}
});
}
private void initTextInputControl(TextInputControl rule, CheckBox ruleEnabled) {
rule.disableProperty().bind(ruleEnabled.selectedProperty().not());
ruleEnabled.selectedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
rule.setText("");
}
});
}
}