├── .gitignore ├── README.md ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── skynet │ │ └── javafx │ │ ├── Application.java │ │ ├── JavaFxApplicationSupport.java │ │ ├── config │ │ └── PropertiesConfig.java │ │ ├── constants │ │ ├── CommonConstants.java │ │ └── FieldConstraints.java │ │ ├── controller │ │ ├── ButtonBarController.java │ │ ├── CrudController.java │ │ ├── CustomerController.java │ │ ├── FrameGridController.java │ │ └── MainController.java │ │ ├── jfxsupport │ │ ├── AbstractFxmlView.java │ │ ├── AbstractJavaFxApplicationSupport.java │ │ ├── FXMLController.java │ │ ├── FXMLView.java │ │ ├── GUIState.java │ │ ├── PropertyReaderHelper.java │ │ ├── PrototypeController.java │ │ ├── SelectKeyComboBoxListener.java │ │ └── SplashScreen.java │ │ ├── model │ │ ├── Customer.java │ │ ├── MenuItem.java │ │ └── SimpleEntity.java │ │ ├── repository │ │ ├── CustomerRepository.java │ │ └── MenuItemRepository.java │ │ ├── service │ │ ├── CustomerService.java │ │ ├── FrameService.java │ │ └── MenuItemService.java │ │ └── views │ │ ├── CustomerView.java │ │ ├── FrameGridView.java │ │ ├── MainView.java │ │ └── def │ │ ├── CustomerGridDef.java │ │ └── FrameGridDef.java └── resources │ └── profiles │ ├── common │ ├── db │ │ └── migration │ │ │ └── V001__create_tables.sql │ ├── fxml │ │ ├── buttonbar.fxml │ │ ├── customer.fxml │ │ ├── framebar.fxml │ │ ├── framegrid.fxml │ │ ├── main.fxml │ │ └── setup.fxml │ └── images │ │ ├── category_16x16.png │ │ ├── category_32x32.png │ │ ├── customers_16x16.png │ │ ├── customers_32x32.png │ │ ├── gear_16x16.png │ │ ├── gear_24x24.png │ │ ├── gear_36x36.png │ │ ├── gear_42x42.png │ │ ├── gear_64x64.png │ │ ├── invoice_16x16.png │ │ ├── invoice_32x32.png │ │ ├── javafx.png │ │ ├── product_16x16.png │ │ └── product_32x32.png │ └── dev │ ├── application.yml │ └── db │ └── migration │ └── R__dev_profile_initialization.sql └── test └── java └── com └── skynet └── javafx └── ApplicationTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Add any directories, files, or patterns you don't want to be tracked by version control 2 | .classpath 3 | .project 4 | /.settings/ 5 | /target/ 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spring Boot + JavaFx 2 | 3 | 4 | ## Getting Started 5 | Example desktop application with JavaFx. 6 | 7 | Clone repository. 8 | ``` 9 | git clone https://github.com/samuelcuellar/spring-boot-javafx.git 10 | ``` 11 | 12 | Run application locally 13 | ```bash 14 | mvn spring-boot:run 15 | ``` 16 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | 6 | skynet 7 | spring-boot-javafx 8 | 0.0.1-SNAPSHOT 9 | jar 10 | spring-boot-javafx 11 | http://maven.apache.org 12 | 13 | 14 | org.springframework.boot 15 | spring-boot-starter-parent 16 | 2.2.2.RELEASE 17 | 18 | 19 | 20 | 21 | UTF-8 22 | 12 23 | 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-data-jpa 33 | 34 | 35 | com.h2database 36 | h2 37 | 38 | 39 | org.flywaydb 40 | flyway-core 41 | 42 | 43 | org.openjfx 44 | javafx-fxml 45 | 14 46 | 47 | 48 | org.openjfx 49 | javafx-controls 50 | 14 51 | 52 | 53 | org.projectlombok 54 | lombok 55 | compile 56 | 57 | 58 | commons-lang 59 | commons-lang 60 | 2.6 61 | 62 | 63 | junit 64 | junit 65 | test 66 | 67 | 68 | 69 | 70 | 71 | dev 72 | 73 | true 74 | 75 | 76 | spring-boot-javafx-dev 77 | 78 | 79 | src/main/resources/profiles/dev 80 | 81 | 82 | src/main/resources/profiles/common 83 | 84 | 85 | 86 | 87 | 88 | pro 89 | 90 | false 91 | 92 | 93 | spring-boot-javafx-pro 94 | 95 | 96 | src/main/resources/profiles/pro 97 | 98 | 99 | src/main/resources/profiles/common 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | org.springframework.boot 110 | spring-boot-maven-plugin 111 | 112 | true 113 | 114 | dev 115 | pro 116 | 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/Application.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx; 2 | 3 | /** 4 | * JavaFx Application, 5 | * 6 | * This is the entry point, an JavaFxApplicationSupport is launched from this main method as a workaround to delegate in Maven dependences the JavaFX libs. 7 | * 8 | * @author samuel.cuellar 9 | */ 10 | 11 | public class Application { 12 | 13 | public static void main(String[] args) { 14 | JavaFxApplicationSupport.main(args); 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/JavaFxApplicationSupport.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.boot.WebApplicationType; 9 | import org.springframework.boot.autoconfigure.SpringBootApplication; 10 | import org.springframework.boot.builder.SpringApplicationBuilder; 11 | import org.springframework.context.ConfigurableApplicationContext; 12 | import com.skynet.javafx.controller.MainController; 13 | import com.skynet.javafx.jfxsupport.AbstractFxmlView; 14 | import com.skynet.javafx.jfxsupport.GUIState; 15 | import com.skynet.javafx.jfxsupport.PropertyReaderHelper; 16 | import com.skynet.javafx.views.MainView; 17 | import javafx.application.Platform; 18 | import javafx.scene.Scene; 19 | import javafx.scene.control.Alert; 20 | import javafx.scene.control.Alert.AlertType; 21 | import javafx.scene.image.Image; 22 | import javafx.stage.Stage; 23 | import javafx.stage.StageStyle; 24 | import javafx.stage.WindowEvent; 25 | 26 | /** 27 | * JavaFxApplicationSupport, an instance of this class is created by Application.main(...) 28 | * 29 | * @author samuel.cuellar 30 | */ 31 | 32 | @SpringBootApplication 33 | public class JavaFxApplicationSupport extends javafx.application.Application { 34 | 35 | private static Logger LOGGER = LoggerFactory.getLogger(JavaFxApplicationSupport.class); 36 | private static ConfigurableApplicationContext applicationContext; 37 | private static List icons = new ArrayList<>(); 38 | 39 | @Override 40 | public void init() throws Exception { 41 | SpringApplicationBuilder builder = new SpringApplicationBuilder(JavaFxApplicationSupport.class); 42 | builder.application().setWebApplicationType(WebApplicationType.NONE); 43 | applicationContext = builder.run(getParameters().getRaw().toArray(new String[0])); 44 | final List fsImages = PropertyReaderHelper.get(applicationContext.getEnvironment(), "javafx.appicons"); 45 | if (!fsImages.isEmpty()) { 46 | fsImages.forEach((s) -> icons.add(new Image(getClass().getResource(s).toExternalForm()))); 47 | } else { 48 | icons.add(new Image(getClass().getResource("/images/gear_16x16.png").toExternalForm())); 49 | icons.add(new Image(getClass().getResource("/images/gear_24x24.png").toExternalForm())); 50 | icons.add(new Image(getClass().getResource("/images/gear_36x36.png").toExternalForm())); 51 | icons.add(new Image(getClass().getResource("/images/gear_42x42.png").toExternalForm())); 52 | icons.add(new Image(getClass().getResource("/images/gear_64x64.png").toExternalForm())); 53 | } 54 | } 55 | 56 | @Override 57 | public void start(Stage primaryStage) throws Exception { 58 | GUIState.setStage(primaryStage); 59 | GUIState.setHostServices(this.getHostServices()); 60 | showInitialView(); 61 | } 62 | 63 | @Override 64 | public void stop() throws Exception { 65 | applicationContext.close(); 66 | } 67 | 68 | private void showInitialView() { 69 | final String stageStyle = applicationContext.getEnvironment().getProperty("javafx.stage.style"); 70 | if (stageStyle != null) { 71 | GUIState.getStage().initStyle(StageStyle.valueOf(stageStyle.toUpperCase())); 72 | } else { 73 | GUIState.getStage().initStyle(StageStyle.DECORATED); 74 | } 75 | showView(MainView.class); 76 | } 77 | 78 | public static void showView(final Class newView) { 79 | try { 80 | final AbstractFxmlView view = applicationContext.getBean(newView); 81 | 82 | if (GUIState.getScene() == null) { 83 | GUIState.setScene(new Scene(view.getView())); 84 | } else { 85 | GUIState.getScene().setRoot(view.getView()); 86 | } 87 | GUIState.getStage().setScene(GUIState.getScene()); 88 | applyEnvPropsToView(); 89 | GUIState.getStage().getIcons().addAll(icons); 90 | GUIState.getStage().addEventHandler(WindowEvent.WINDOW_SHOWN, e -> { 91 | if (view.getFxmlLoader().getController() instanceof MainController) { 92 | MainController mainController = (MainController) view.getFxmlLoader().getController(); 93 | mainController.onWindowShownEvent(); 94 | } 95 | LOGGER.debug("Stage view shown: {} ", view.getClass()); 96 | }); 97 | GUIState.getStage().show(); 98 | } catch (Throwable t) { 99 | LOGGER.error("Failed to load application: ", t); 100 | showErrorAlert(t); 101 | } 102 | } 103 | 104 | private static void applyEnvPropsToView() { 105 | PropertyReaderHelper.setIfPresent(applicationContext.getEnvironment(), "javafx.title", String.class, GUIState.getStage()::setTitle); 106 | PropertyReaderHelper.setIfPresent(applicationContext.getEnvironment(), "javafx.stage.width", Double.class, GUIState.getStage()::setWidth); 107 | PropertyReaderHelper.setIfPresent(applicationContext.getEnvironment(), "javafx.stage.height", Double.class, GUIState.getStage()::setHeight); 108 | PropertyReaderHelper.setIfPresent(applicationContext.getEnvironment(), "javafx.stage.resizable", Boolean.class, GUIState.getStage()::setResizable); 109 | } 110 | 111 | private static void showErrorAlert(Throwable throwable) { 112 | Alert alert = new Alert(AlertType.ERROR, "Oops! An unrecoverable error occurred.\n" + "Please contact your software vendor.\n\n" + "The application will stop now."); 113 | alert.showAndWait().ifPresent(response -> Platform.exit()); 114 | } 115 | 116 | public static void main(String[] args) { 117 | launch(args); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/config/PropertiesConfig.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.config; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | import lombok.Data; 7 | 8 | @Data 9 | @Configuration 10 | public class PropertiesConfig { 11 | 12 | @Value("${javafx.main.tree}") 13 | private Boolean javafxMainTree; 14 | 15 | @Value("${javafx.main.toolbar}") 16 | private Boolean javafxMainToolbar; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/constants/CommonConstants.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.constants; 2 | 3 | public class CommonConstants { 4 | 5 | public static final long SERIAL_VERSION_UID = 204l; 6 | 7 | public static final String EMPTY_STRING = ""; 8 | 9 | public static final String COLUMN_CENTER_STYLE = "-fx-alignment: CENTER;"; 10 | 11 | public static final String CURRENCY_FORMAT = "%.2f"; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/constants/FieldConstraints.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.constants; 2 | /** 3 | * Convenience class to keep all field restrictions together 4 | * @author samuel.cuellar 5 | * 6 | */ 7 | public class FieldConstraints { 8 | 9 | public static final int CHAR = 1; 10 | public static final int SHORT_STRING_MIN = 3; 11 | public static final int SHORT_STRING_MAX = 20; 12 | public static final int MEDIUM_STRING_MAX = 50; 13 | public static final int LONG_STRING_MAX = 100; 14 | public static final int LONG_STRING_MAX_250 = 250; 15 | public static final int STANDARD_STRING_MAX = 255; 16 | public static final int LONG_DESCRIPTION_STRING_MAX = 512; 17 | public static final int VERY_LONG_STRING_MAX = 4096; 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/controller/ButtonBarController.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.controller; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import com.skynet.javafx.jfxsupport.FXMLController; 6 | import javafx.fxml.FXML; 7 | import javafx.scene.control.Button; 8 | import javafx.stage.Window; 9 | 10 | @FXMLController 11 | public class ButtonBarController { 12 | 13 | private static final Logger logger = LoggerFactory.getLogger(ButtonBarController.class); 14 | 15 | private CrudController target; 16 | 17 | @FXML 18 | protected Button acceptButton; 19 | 20 | @FXML 21 | protected Button cancelButton; 22 | 23 | @FXML 24 | private void initialize() { 25 | logger.debug("initialize ButtonBarController"); 26 | 27 | acceptButton.setOnAction((event) -> { acceptButtonHandleAction(); }); 28 | acceptButton.defaultButtonProperty().bind(acceptButton.focusedProperty()); 29 | 30 | cancelButton.setOnAction((event) -> { cancelButtonHandleAction(); }); 31 | cancelButton.defaultButtonProperty().bind(cancelButton.focusedProperty()); 32 | } 33 | 34 | private void acceptButtonHandleAction() { 35 | Window stage = acceptButton.getScene().getWindow(); 36 | target.save(); 37 | stage.hide(); 38 | } 39 | 40 | private void cancelButtonHandleAction() { 41 | Window stage = cancelButton.getScene().getWindow(); 42 | stage.hide(); 43 | } 44 | 45 | public void setTarget(CrudController target) { 46 | this.target = target; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/controller/CrudController.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.controller; 2 | 3 | import com.skynet.javafx.model.SimpleEntity; 4 | 5 | public interface CrudController { 6 | 7 | public void add(); 8 | 9 | public void render(SimpleEntity id); 10 | 11 | public void save(); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/controller/CustomerController.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.controller; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import com.skynet.javafx.jfxsupport.FXMLController; 7 | import com.skynet.javafx.model.Customer; 8 | import com.skynet.javafx.model.SimpleEntity; 9 | import com.skynet.javafx.service.CustomerService; 10 | import javafx.fxml.FXML; 11 | import javafx.scene.control.TextField; 12 | 13 | @FXMLController 14 | public class CustomerController implements CrudController { 15 | 16 | private static final Logger logger = LoggerFactory.getLogger(CustomerController.class); 17 | 18 | @FXML 19 | private ButtonBarController buttonbarController; 20 | 21 | @FXML 22 | private TextField textFirstname; 23 | @FXML 24 | private TextField textLastname; 25 | @FXML 26 | private TextField textAddress; 27 | @FXML 28 | private TextField textEmail; 29 | 30 | @Autowired 31 | private CustomerService customerService; 32 | 33 | private Customer customer; 34 | 35 | @FXML 36 | private void initialize() { 37 | logger.debug("initialize CustomerController"); 38 | 39 | buttonbarController.setTarget(this); 40 | } 41 | 42 | @Override 43 | public void add() { 44 | // TODO Auto-generated method stub 45 | } 46 | 47 | @Override 48 | public void render(SimpleEntity entity) { 49 | customer = (Customer) entity; 50 | textFirstname.setText(customer.getFirstname()); 51 | textLastname.setText(customer.getLastname()); 52 | textAddress.setText(customer.getAddress()); 53 | textEmail.setText(customer.getEmail()); 54 | } 55 | 56 | @Override 57 | public void save() { 58 | if (customer == null) { 59 | customer = new Customer(); 60 | } 61 | customer.setFirstname(textFirstname.getText()); 62 | customer.setLastname(textLastname.getText()); 63 | customer.setAddress(textAddress.getText()); 64 | customer.setEmail(textEmail.getText()); 65 | customerService.save(customer); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/controller/FrameGridController.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.controller; 2 | 3 | import java.util.List; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.context.ApplicationContext; 9 | import org.springframework.context.annotation.Scope; 10 | import com.skynet.javafx.jfxsupport.AbstractFxmlView; 11 | import com.skynet.javafx.jfxsupport.FXMLController; 12 | import com.skynet.javafx.jfxsupport.PrototypeController; 13 | import com.skynet.javafx.model.SimpleEntity; 14 | import com.skynet.javafx.service.FrameService; 15 | import com.skynet.javafx.views.def.FrameGridDef; 16 | import javafx.collections.FXCollections; 17 | import javafx.collections.ObservableList; 18 | import javafx.fxml.FXML; 19 | import javafx.scene.Scene; 20 | import javafx.scene.control.Button; 21 | import javafx.scene.control.TableColumn; 22 | import javafx.scene.control.TableView; 23 | import javafx.scene.control.cell.PropertyValueFactory; 24 | import javafx.scene.input.KeyCode; 25 | import javafx.stage.Modality; 26 | import javafx.stage.Stage; 27 | import javafx.stage.StageStyle; 28 | 29 | @FXMLController 30 | @Scope("prototype") 31 | public class FrameGridController implements PrototypeController { 32 | 33 | private static final Logger logger = LoggerFactory.getLogger(FrameGridController.class); 34 | 35 | @Autowired 36 | private ApplicationContext context; 37 | @FXML 38 | private Button addButton; 39 | @FXML 40 | private Button editButton; 41 | @FXML 42 | private Button deleteButton; 43 | @FXML 44 | private Button printButton; 45 | @FXML 46 | private TableView frameGrid; 47 | private FrameService frameService; 48 | private FrameGridDef gridDef; 49 | private Scene scene; 50 | 51 | @FXML 52 | private void initialize() { 53 | editButton.setDisable(true); 54 | deleteButton.setDisable(true); 55 | printButton.setDisable(true); 56 | addButton.setOnAction((event) -> { addButtonHandleAction(); }); 57 | editButton.setOnAction((event) -> { editButtonHandleAction(); }); 58 | deleteButton.setOnAction((event) -> { deleteButtonHandleAction(); }); 59 | printButton.setOnAction((event) -> { printButtonHandleAction(); }); 60 | } 61 | 62 | private void addButtonHandleAction() { 63 | AbstractFxmlView fxmlView = showDialog(); 64 | CrudController controller = (CrudController) fxmlView.getFxmlLoader().getController(); 65 | controller.add(); 66 | } 67 | 68 | private void editButtonHandleAction() { 69 | AbstractFxmlView fxmlView = showDialog(); 70 | CrudController controller = (CrudController) fxmlView.getFxmlLoader().getController(); 71 | SimpleEntity entity = frameGrid.getSelectionModel().getSelectedItem(); 72 | controller.render(entity); 73 | } 74 | 75 | private void deleteButtonHandleAction() { 76 | SimpleEntity entity = frameGrid.getSelectionModel().getSelectedItem(); 77 | frameService.delete(entity.getId()); 78 | loadData(); 79 | } 80 | 81 | private void printButtonHandleAction() { 82 | logger.debug("clicked printButton"); 83 | } 84 | 85 | public void initializeGrid(FrameService frameService, FrameGridDef gridDef) { 86 | this.frameService = frameService; 87 | this.gridDef = gridDef; 88 | setupGrid(); 89 | loadData(); 90 | } 91 | 92 | private void setupGrid() { 93 | List columnNames = gridDef.getColumnNames(); 94 | List columnDataNames = gridDef.getColumnDataName(); 95 | List columnSizes = gridDef.getColumnSizes(); 96 | for (int i = 0; i < gridDef.getColumnNames().size(); i++) { 97 | TableColumn column = new TableColumn<>(columnNames.get(i)); 98 | column.setCellValueFactory( 99 | new PropertyValueFactory(columnDataNames.get(i)) 100 | ); 101 | column.setMinWidth(columnSizes.get(i)); 102 | frameGrid.getColumns().add(column); 103 | } 104 | frameGrid.getSelectionModel().selectedItemProperty().addListener((obs, oldSelection, newSelection) -> { 105 | if (newSelection != null) { 106 | editButton.setDisable(false); 107 | deleteButton.setDisable(false); 108 | } else { 109 | editButton.setDisable(true); 110 | deleteButton.setDisable(true); 111 | } 112 | }); 113 | frameGrid.setOnKeyPressed(e -> { 114 | if (e.getCode() == KeyCode.ENTER) { editButtonHandleAction(); } 115 | }); 116 | frameGrid.setOnMousePressed((event) -> { 117 | if (event.isPrimaryButtonDown() && event.getClickCount() == 2) { editButtonHandleAction(); } 118 | }); 119 | } 120 | 121 | private void loadData() { 122 | ObservableList data = FXCollections.observableArrayList(frameService.getData()); 123 | if (data != null) { 124 | logger.debug("loadData, data size: {}", data.size()); 125 | frameGrid.setItems(data); 126 | } 127 | } 128 | 129 | private AbstractFxmlView showDialog() { 130 | AbstractFxmlView fxmlView = (AbstractFxmlView) context.getBean(gridDef.getCreateView()); 131 | Stage stage = new Stage(); 132 | scene = new Scene(fxmlView.getView()); 133 | stage.setScene(scene); 134 | stage.initModality(Modality.APPLICATION_MODAL); 135 | stage.initStyle(StageStyle.UTILITY); 136 | stage.setResizable(false); 137 | stage.setTitle(gridDef.getTitlePopups()); 138 | stage.setOnHidden((event) -> { 139 | stage.close(); 140 | SimpleEntity oldSelected = frameGrid.getSelectionModel().getSelectedItem(); 141 | loadData(); 142 | if (oldSelected != null) { 143 | frameGrid.getSelectionModel().select(oldSelected); 144 | } else { 145 | frameGrid.getSelectionModel().select(0); 146 | } 147 | }); 148 | stage.show(); 149 | return fxmlView; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/controller/MainController.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.controller; 2 | 3 | import java.util.List; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.context.ApplicationContext; 6 | import com.skynet.javafx.config.PropertiesConfig; 7 | import com.skynet.javafx.jfxsupport.FXMLController; 8 | import com.skynet.javafx.service.FrameService; 9 | import com.skynet.javafx.service.MenuItemService; 10 | import com.skynet.javafx.views.FrameGridView; 11 | import com.skynet.javafx.views.def.FrameGridDef; 12 | import javafx.fxml.FXML; 13 | import javafx.geometry.Rectangle2D; 14 | import javafx.scene.control.Button; 15 | import javafx.scene.control.MultipleSelectionModel; 16 | import javafx.scene.control.SplitPane; 17 | import javafx.scene.control.Tab; 18 | import javafx.scene.control.TabPane; 19 | import javafx.scene.control.ToolBar; 20 | import javafx.scene.control.Tooltip; 21 | import javafx.scene.control.TreeItem; 22 | import javafx.scene.control.TreeView; 23 | import javafx.scene.image.Image; 24 | import javafx.scene.image.ImageView; 25 | import javafx.scene.input.KeyCode; 26 | import javafx.scene.layout.VBox; 27 | import javafx.stage.Screen; 28 | 29 | @FXMLController 30 | public class MainController { 31 | 32 | private static String ADDITIONAL_TAB_TITLE = " "; 33 | 34 | @Autowired 35 | private ApplicationContext context; 36 | 37 | @Autowired 38 | private PropertiesConfig config; 39 | 40 | @Autowired 41 | private MenuItemService menuItemService; 42 | 43 | @FXML 44 | private VBox mainPanel; 45 | 46 | @FXML 47 | private SplitPane mainSplitPanel; 48 | 49 | @FXML 50 | private TabPane tabPane; 51 | 52 | TreeView treeView; 53 | 54 | @FXML 55 | private void initialize() { 56 | Rectangle2D primaryScreenBounds = Screen.getPrimary().getVisualBounds(); 57 | mainPanel.setPrefWidth(primaryScreenBounds.getWidth() * 0.8); 58 | mainPanel.setPrefHeight(primaryScreenBounds.getHeight() * 0.8); 59 | 60 | if (config.getJavafxMainToolbar()) { 61 | ToolBar toolbar = buildToolBar(); 62 | mainPanel.getChildren().add(1, toolbar); 63 | } 64 | 65 | if (config.getJavafxMainTree()) { 66 | com.skynet.javafx.model.MenuItem rootMenuItem = menuItemService.getMenuItemRoot(); 67 | TreeItem rootNode = new TreeItem<>(); 68 | rootNode.setValue(rootMenuItem); 69 | rootNode.setExpanded(rootMenuItem.getExpanded()); 70 | treeView = new TreeView<>(); 71 | treeView.setRoot(rootNode); 72 | buildTreeItems(rootNode); 73 | mainSplitPanel.getItems().add(0, treeView); 74 | treeView.setOnKeyPressed(e -> { 75 | if (e.getCode() == KeyCode.ENTER) { 76 | onSelectItemAction(treeView); 77 | } 78 | }); 79 | treeView.setOnMouseClicked((event) -> { 80 | if (event.getClickCount() == 2) { 81 | onSelectItemAction(treeView); 82 | } 83 | }); 84 | } 85 | 86 | } 87 | 88 | public void onWindowShownEvent() { 89 | if (treeView != null) { 90 | MultipleSelectionModel> msm = treeView.getSelectionModel(); 91 | msm.select(treeView.getRoot()); 92 | treeView.requestFocus(); 93 | }; 94 | } 95 | 96 | 97 | private void buildTreeItems(TreeItem parentNode) { 98 | Long parentId = -1l; 99 | if (parentNode.getValue() != null) { 100 | parentId = parentNode.getValue().getId(); 101 | } 102 | List menuItems = menuItemService.getMenuItemsByParent(parentId); 103 | for (com.skynet.javafx.model.MenuItem item : menuItems) { 104 | TreeItem itemNode = new TreeItem<>(); 105 | itemNode.setValue(item); 106 | itemNode.setExpanded(item.getExpanded()); 107 | if (!item.getImage().isEmpty()) { 108 | itemNode.setGraphic(new ImageView(new Image(getClass().getResourceAsStream("/images/" + item.getImage())))); 109 | } 110 | parentNode.getChildren().add(itemNode); 111 | buildTreeItems(itemNode); 112 | } 113 | } 114 | 115 | private void onSelectItemAction(TreeView treeView) { 116 | TreeItem item = treeView.getSelectionModel().getSelectedItem(); 117 | if (item == null) 118 | return; 119 | int tabIndex = findTabIndex(item.getValue().getValue()); 120 | if (tabIndex == -1) { 121 | tabPane.getTabs().contains((Object) item.getValue()); 122 | showItemContent(item.getValue()); 123 | } else { 124 | tabPane.getSelectionModel().select(tabIndex); 125 | } 126 | } 127 | 128 | private void showItemContent(com.skynet.javafx.model.MenuItem menuItem) { 129 | if (menuItem.getService() == null) { 130 | return; 131 | } 132 | 133 | FrameGridView gridView = (FrameGridView) context.getBean(FrameGridView.class); 134 | FrameGridController controller = (FrameGridController) context.getBean(FrameGridController.class); 135 | gridView.setController(controller); 136 | 137 | Tab tab = new Tab(menuItem.getValue() + ADDITIONAL_TAB_TITLE); 138 | tab.setContent(gridView.getView()); 139 | tabPane.getTabs().add(tab); 140 | tabPane.getSelectionModel().select(tabPane.getTabs().size() - 1); 141 | 142 | FrameService frameService = (FrameService) context.getBean(menuItem.getService()); 143 | FrameGridDef frameGridDef = (FrameGridDef) context.getBean(menuItem.getGridDef()); 144 | controller.initializeGrid(frameService, frameGridDef); 145 | } 146 | 147 | private ToolBar buildToolBar() { 148 | ToolBar toolbar = new ToolBar(); 149 | List menuItems = menuItemService.getMenuItemsByParent(0l); 150 | for (com.skynet.javafx.model.MenuItem item : menuItems) { 151 | Button button = new Button(); 152 | button.setTooltip(new Tooltip(item.getValue())); 153 | if (!item.getImage().isEmpty()) { 154 | button.setGraphic(new ImageView(new Image(getClass().getResourceAsStream("/images/" + item.getImage())))); 155 | } 156 | button.setOnAction((event) -> { onToolBarButtonAction(item); }); 157 | toolbar.getItems().add(button); 158 | } 159 | return toolbar; 160 | } 161 | 162 | private void onToolBarButtonAction(com.skynet.javafx.model.MenuItem item) { 163 | int tabIndex = findTabIndex(item.getValue()); 164 | if (tabIndex == -1) { 165 | tabPane.getTabs().contains((Object) item.getValue()); 166 | showItemContent(item); 167 | } else { 168 | tabPane.getSelectionModel().select(tabIndex); 169 | } 170 | } 171 | 172 | private int findTabIndex(String title) { 173 | for (int i = 0; i < tabPane.getTabs().size(); i++) { 174 | Tab tab = tabPane.getTabs().get(i); 175 | if (tab.getText().equals(title + ADDITIONAL_TAB_TITLE)) { 176 | return i; 177 | } 178 | } 179 | return -1; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/jfxsupport/AbstractFxmlView.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.jfxsupport; 2 | 3 | import static java.util.ResourceBundle.getBundle; 4 | 5 | import java.io.IOException; 6 | import java.net.URL; 7 | import java.util.List; 8 | import java.util.MissingResourceException; 9 | import java.util.ResourceBundle; 10 | import java.util.concurrent.CompletableFuture; 11 | import java.util.function.Consumer; 12 | 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import org.springframework.beans.BeansException; 16 | import org.springframework.context.ApplicationContext; 17 | import org.springframework.context.ApplicationContextAware; 18 | import org.springframework.util.StringUtils; 19 | 20 | import javafx.application.Platform; 21 | import javafx.beans.property.ObjectProperty; 22 | import javafx.beans.property.SimpleObjectProperty; 23 | import javafx.beans.value.ObservableValue; 24 | import javafx.collections.ObservableList; 25 | import javafx.fxml.FXMLLoader; 26 | import javafx.scene.Node; 27 | import javafx.scene.Parent; 28 | import javafx.scene.layout.AnchorPane; 29 | import javafx.util.Callback; 30 | 31 | /** 32 | * Base class for fxml-based view classes. 33 | * 34 | * It is derived from Adam Bien's 35 | * afterburner.fx project. 36 | *

37 | * {@link AbstractFxmlView} is a stripped down version of FXMLView that provides DI for Java FX Controllers via Spring. 40 | *

41 | *

42 | * Supports annotation driven creation of FXML based view beans with {@link FXMLView} 43 | *

44 | * 45 | * @author Thomas Darimont 46 | * @author Felix Roske 47 | * @author Andreas Jay 48 | * 49 | */ 50 | public abstract class AbstractFxmlView implements ApplicationContextAware { 51 | 52 | private static final Logger LOGGER = LoggerFactory.getLogger(AbstractFxmlView.class); 53 | 54 | private final ObjectProperty presenterProperty; 55 | 56 | private final ResourceBundle bundle; 57 | 58 | private final URL resource; 59 | 60 | private final FXMLView annotation; 61 | 62 | private PrototypeController controller; 63 | 64 | private FXMLLoader fxmlLoader; 65 | 66 | private ApplicationContext applicationContext; 67 | 68 | private String fxmlRoot; 69 | 70 | 71 | /** 72 | * Instantiates a new abstract fxml view. 73 | */ 74 | public AbstractFxmlView() { 75 | LOGGER.debug("AbstractFxmlView construction"); 76 | // Set the root path to package path 77 | final String filePathFromPackageName = PropertyReaderHelper.determineFilePathFromPackageName(getClass()); 78 | setFxmlRootPath(filePathFromPackageName); 79 | annotation = getFXMLAnnotation(); 80 | resource = getURLResource(annotation); 81 | presenterProperty = new SimpleObjectProperty<>(); 82 | bundle = getResourceBundle(getBundleName()); 83 | } 84 | 85 | /** 86 | * Gets the URL resource. This will be derived from applied annotation value 87 | * or from naming convention. 88 | * 89 | * @param annotation 90 | * the annotation as defined by inheriting class. 91 | * @return the URL resource 92 | */ 93 | private URL getURLResource(final FXMLView annotation) { 94 | if (annotation != null && !annotation.value().equals("")) { 95 | return getClass().getResource(annotation.value()); 96 | } else { 97 | return getClass().getResource(getFxmlPath()); 98 | } 99 | } 100 | 101 | /** 102 | * Gets the {@link FXMLView} annotation from inheriting class. 103 | * 104 | * @return the FXML annotation 105 | */ 106 | private FXMLView getFXMLAnnotation() { 107 | final Class theClass = this.getClass(); 108 | final FXMLView annotation = theClass.getAnnotation(FXMLView.class); 109 | return annotation; 110 | } 111 | 112 | /** 113 | * Creates the controller for type. 114 | * 115 | * @param type 116 | * the type 117 | * @return the object 118 | */ 119 | private Object createControllerForType(final Class type) { 120 | return applicationContext.getBean(type); 121 | } 122 | 123 | /* 124 | * (non-Javadoc) 125 | * 126 | * @see 127 | * org.springframework.context.ApplicationContextAware#setApplicationContext 128 | * (org.springframework.context.ApplicationContext) 129 | */ 130 | @Override 131 | public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException { 132 | 133 | if (this.applicationContext != null) { 134 | return; 135 | } 136 | 137 | this.applicationContext = applicationContext; 138 | } 139 | 140 | /** 141 | * Sets the fxml root path. 142 | * 143 | * @param path 144 | * the new fxml root path 145 | */ 146 | private void setFxmlRootPath(final String path) { 147 | if (path.endsWith("/")) { 148 | fxmlRoot = path; 149 | } else { 150 | fxmlRoot = path + "/"; 151 | } 152 | } 153 | 154 | /** 155 | * Load synchronously. 156 | * 157 | * @param resource 158 | * the resource 159 | * @param bundle 160 | * the bundle 161 | * @return the FXML loader 162 | * @throws IllegalStateException 163 | * the illegal state exception 164 | */ 165 | private FXMLLoader loadSynchronously(final URL resource, final ResourceBundle bundle, 166 | final PrototypeController controller) throws IllegalStateException { 167 | 168 | final FXMLLoader loader = new FXMLLoader(resource, bundle); 169 | 170 | if (controller != null) { 171 | loader.setControllerFactory(new Callback, Object>() { 172 | @Override 173 | public Object call(Class aClass) { 174 | return controller; 175 | } 176 | }); 177 | } else { 178 | loader.setControllerFactory(this::createControllerForType); 179 | } 180 | 181 | try { 182 | loader.load(); 183 | } catch (final IOException ex) { 184 | throw new IllegalStateException("Cannot load " + getConventionalName(), ex); 185 | } 186 | 187 | return loader; 188 | } 189 | 190 | /** 191 | * Ensure fxml loader initialized. 192 | */ 193 | private void ensureFxmlLoaderInitialized() { 194 | 195 | if (fxmlLoader != null) { 196 | return; 197 | } 198 | 199 | fxmlLoader = loadSynchronously(resource, bundle, controller); 200 | presenterProperty.set(fxmlLoader.getController()); 201 | } 202 | 203 | /** 204 | * Initializes the view by loading the FXML (if not happened yet) and 205 | * returns the top Node (parent) specified in the FXML file. 206 | * 207 | * @return the root view as determined from {@link FXMLLoader}. 208 | */ 209 | public Parent getView() { 210 | 211 | ensureFxmlLoaderInitialized(); 212 | 213 | final Parent parent = fxmlLoader.getRoot(); 214 | addCSSIfAvailable(parent); 215 | return parent; 216 | } 217 | 218 | /** 219 | * Initializes the view synchronously and invokes the consumer with the 220 | * created parent Node within the FX UI thread. 221 | * 222 | * @param consumer 223 | * - an object interested in received the {@link Parent} as 224 | * callback 225 | */ 226 | public void getView(final Consumer consumer) { 227 | CompletableFuture.supplyAsync(this::getView, Platform::runLater).thenAccept(consumer); 228 | } 229 | 230 | /** 231 | * Scene Builder creates for each FXML document a root container. This 232 | * method omits the root container (e.g. {@link AnchorPane}) and gives you 233 | * the access to its first child. 234 | * 235 | * @return the first child of the {@link AnchorPane} or null if there are no 236 | * children available from this view. 237 | */ 238 | public Node getViewWithoutRootContainer() { 239 | 240 | final ObservableList children = getView().getChildrenUnmodifiable(); 241 | if (children.isEmpty()) { 242 | return null; 243 | } 244 | 245 | return children.listIterator().next(); 246 | } 247 | 248 | /** 249 | * Adds the CSS if available. 250 | * 251 | * @param parent 252 | * the parent 253 | */ 254 | void addCSSIfAvailable(final Parent parent) { 255 | 256 | // Read global css when available: 257 | final List list = PropertyReaderHelper.get(applicationContext.getEnvironment(), "javafx.css"); 258 | if (!list.isEmpty()) { 259 | list.forEach(css -> parent.getStylesheets().add(getClass().getResource(css).toExternalForm())); 260 | } 261 | 262 | addCSSFromAnnotation(parent, annotation); 263 | 264 | final URL uri = getClass().getResource(getStyleSheetName()); 265 | if (uri == null) { 266 | return; 267 | } 268 | 269 | final String uriToCss = uri.toExternalForm(); 270 | parent.getStylesheets().add(uriToCss); 271 | } 272 | 273 | /** 274 | * Adds the CSS from annotation to parent. 275 | * 276 | * @param parent 277 | * the parent 278 | * @param annotation 279 | * the annotation 280 | */ 281 | private void addCSSFromAnnotation(final Parent parent, final FXMLView annotation) { 282 | if (annotation != null && annotation.css().length > 0) { 283 | for (final String cssFile : annotation.css()) { 284 | final URL uri = getClass().getResource(cssFile); 285 | if (uri != null) { 286 | final String uriToCss = uri.toExternalForm(); 287 | parent.getStylesheets().add(uriToCss); 288 | LOGGER.debug("css file added to parent: {}", cssFile); 289 | } else { 290 | LOGGER.warn("referenced {} css file could not be located", cssFile); 291 | } 292 | } 293 | } 294 | } 295 | 296 | /** 297 | * Gets the style sheet name. 298 | * 299 | * @return the style sheet name 300 | */ 301 | private String getStyleSheetName() { 302 | return fxmlRoot + getConventionalName(".css"); 303 | } 304 | 305 | /** 306 | * In case the view was not initialized yet, the conventional fxml 307 | * (airhacks.fxml for the AirhacksView and AirhacksPresenter) are loaded and 308 | * the specified presenter / controller is going to be constructed and 309 | * returned. 310 | * 311 | * @return the corresponding controller / presenter (usually for a 312 | * AirhacksView the AirhacksPresenter) 313 | */ 314 | public Object getPresenter() { 315 | 316 | ensureFxmlLoaderInitialized(); 317 | 318 | return presenterProperty.get(); 319 | } 320 | 321 | /** 322 | * Does not initialize the view. Only registers the Consumer and waits until 323 | * the the view is going to be created / the method FXMLView#getView or 324 | * FXMLView#getViewAsync invoked. 325 | * 326 | * @param presenterConsumer 327 | * listener for the presenter construction 328 | */ 329 | public void getPresenter(final Consumer presenterConsumer) { 330 | 331 | presenterProperty.addListener( 332 | (final ObservableValue o, final Object oldValue, final Object newValue) -> { 333 | presenterConsumer.accept(newValue); 334 | }); 335 | } 336 | 337 | /** 338 | * Gets the conventional name. 339 | * 340 | * @param ending 341 | * the suffix to append 342 | * @return the conventional name with stripped ending 343 | */ 344 | private String getConventionalName(final String ending) { 345 | return getConventionalName() + ending; 346 | } 347 | 348 | /** 349 | * Gets the conventional name. 350 | * 351 | * @return the name of the view without the "View" prefix in lowerCase. For 352 | * AirhacksView just airhacks is going to be returned. 353 | */ 354 | private String getConventionalName() { 355 | return stripEnding(getClass().getSimpleName().toLowerCase()); 356 | } 357 | 358 | /** 359 | * Gets the bundle name. 360 | * 361 | * @return the bundle name 362 | */ 363 | private String getBundleName() { 364 | if (!StringUtils.isEmpty(annotation)) { 365 | final String lbundle = annotation.bundle(); 366 | LOGGER.debug("Annotated bundle: {}", lbundle); 367 | return lbundle; 368 | } else { 369 | final String lbundle = getClass().getPackage().getName() + "." + getConventionalName(); 370 | LOGGER.debug("Bundle: {} based on conventional name.", lbundle); 371 | return lbundle; 372 | } 373 | } 374 | 375 | /** 376 | * Strip ending. 377 | * 378 | * @param clazz 379 | * the clazz 380 | * @return the string 381 | */ 382 | private static String stripEnding(final String clazz) { 383 | 384 | if (!clazz.endsWith("view")) { 385 | return clazz; 386 | } 387 | 388 | return clazz.substring(0, clazz.lastIndexOf("view")); 389 | } 390 | 391 | /** 392 | * Gets the fxml file path. 393 | * 394 | * @return the relative path to the fxml file derived from the FXML view. 395 | * e.g. The name for the AirhacksView is going to be 396 | * /airhacks.fxml. 397 | */ 398 | 399 | final String getFxmlPath() { 400 | final String fxmlPath = fxmlRoot + getConventionalName(".fxml"); 401 | LOGGER.debug("Determined fxmlPath: " + fxmlPath); 402 | return fxmlPath; 403 | } 404 | 405 | /** 406 | * Gets the resource bundle or returns null. 407 | * 408 | * @param name 409 | * the name of the resource bundle. 410 | * @return the resource bundle 411 | */ 412 | private ResourceBundle getResourceBundle(final String name) { 413 | try { 414 | LOGGER.debug("Resource bundle: " + name); 415 | return getBundle(name); 416 | } catch (final MissingResourceException ex) { 417 | LOGGER.debug("No resource bundle could be determined: " + ex.getMessage()); 418 | return null; 419 | } 420 | } 421 | 422 | /** 423 | * Gets the resource bundle. 424 | * 425 | * @return an existing resource bundle, or null 426 | */ 427 | public ResourceBundle getResourceBundle() { 428 | return bundle; 429 | } 430 | 431 | /** 432 | * Gets the fxml loader. 433 | * 434 | * @return the FXMLLoader 435 | */ 436 | public FXMLLoader getFxmlLoader() { 437 | return fxmlLoader; 438 | } 439 | 440 | /** 441 | * Set the controller 442 | * 443 | * @param controller 444 | */ 445 | public void setController(PrototypeController controller) { 446 | this.controller = controller; 447 | } 448 | 449 | @Override 450 | public String toString() { 451 | return "AbstractFxmlView [presenterProperty=" + presenterProperty + ", bundle=" + bundle + ", resource=" 452 | + resource + ", fxmlRoot=" + fxmlRoot + "]"; 453 | } 454 | 455 | } 456 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/jfxsupport/AbstractJavaFxApplicationSupport.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.jfxsupport; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.concurrent.CompletableFuture; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.boot.SpringApplication; 10 | import org.springframework.context.ConfigurableApplicationContext; 11 | 12 | import javafx.application.Application; 13 | import javafx.application.HostServices; 14 | import javafx.application.Platform; 15 | import javafx.beans.property.BooleanProperty; 16 | import javafx.beans.property.SimpleBooleanProperty; 17 | import javafx.scene.Scene; 18 | import javafx.scene.control.Alert; 19 | import javafx.scene.control.Alert.AlertType; 20 | import javafx.scene.image.Image; 21 | import javafx.stage.Stage; 22 | import javafx.stage.StageStyle; 23 | 24 | /** 25 | * The Class AbstractJavaFxApplicationSupport. 26 | * 27 | * TODO: remove this class when all the code is moved to JavaFxApplicationSupport 28 | * 29 | * @author Felix Roske 30 | */ 31 | public abstract class AbstractJavaFxApplicationSupport extends Application { 32 | private static Logger LOGGER = LoggerFactory.getLogger(AbstractJavaFxApplicationSupport.class); 33 | 34 | private static String[] savedArgs = new String[0]; 35 | 36 | private static Class savedInitialView; 37 | 38 | private static ConfigurableApplicationContext applicationContext; 39 | 40 | /** The splash screen. */ 41 | private static SplashScreen splashScreen; 42 | 43 | private static List icons = new ArrayList<>(); 44 | 45 | private final BooleanProperty appCtxLoaded = new SimpleBooleanProperty(false); 46 | 47 | public static Stage getStage() { 48 | return GUIState.getStage(); 49 | } 50 | 51 | public static Scene getScene() { 52 | return GUIState.getScene(); 53 | } 54 | 55 | public static HostServices getAppHostServices() { 56 | return GUIState.getHostServices(); 57 | } 58 | 59 | /* 60 | * (non-Javadoc) 61 | * 62 | * @see javafx.application.Application#init() 63 | */ 64 | @Override 65 | public void init() throws Exception { 66 | CompletableFuture.supplyAsync(() -> { 67 | final ConfigurableApplicationContext ctx = SpringApplication.run(this.getClass(), savedArgs); 68 | 69 | final List fsImages = PropertyReaderHelper.get(ctx.getEnvironment(), "javafx.appicons"); 70 | 71 | if (!fsImages.isEmpty()) { 72 | fsImages.forEach((s) -> icons.add(new Image(getClass().getResource(s).toExternalForm()))); 73 | } else { // add factory images 74 | icons.add(new Image(getClass().getResource("/images/gear_16x16.png").toExternalForm())); 75 | icons.add(new Image(getClass().getResource("/images/gear_24x24.png").toExternalForm())); 76 | icons.add(new Image(getClass().getResource("/images/gear_36x36.png").toExternalForm())); 77 | icons.add(new Image(getClass().getResource("/images/gear_42x42.png").toExternalForm())); 78 | icons.add(new Image(getClass().getResource("/images/gear_64x64.png").toExternalForm())); 79 | } 80 | return ctx; 81 | }).whenComplete((ctx, throwable) -> { 82 | if(throwable != null) { 83 | LOGGER.error("Failed to load spring application context: ", throwable); 84 | Platform.runLater(() -> showErrorAlert(throwable)); 85 | } 86 | else { 87 | launchApplicationView(ctx); 88 | } 89 | }); 90 | } 91 | 92 | /* 93 | * (non-Javadoc) 94 | * 95 | * @see javafx.application.Application#start(javafx.stage.Stage) 96 | */ 97 | @Override 98 | public void start(final Stage stage) throws Exception { 99 | GUIState.setStage(stage); 100 | GUIState.setHostServices(this.getHostServices()); 101 | final Stage splashStage = new Stage(StageStyle.UNDECORATED); 102 | 103 | if (AbstractJavaFxApplicationSupport.splashScreen.visible()) { 104 | final Scene splashScene = new Scene(splashScreen.getParent()); 105 | splashStage.setScene(splashScene); 106 | splashStage.show(); 107 | } 108 | 109 | final Runnable showMainAndCloseSplash = () -> { 110 | showInitialView(); 111 | if (AbstractJavaFxApplicationSupport.splashScreen.visible()) { 112 | splashStage.hide(); 113 | splashStage.setScene(null); 114 | } 115 | }; 116 | 117 | synchronized (this) { 118 | if (appCtxLoaded.get()) { 119 | // Spring ContextLoader was faster 120 | Platform.runLater(showMainAndCloseSplash); 121 | } else { 122 | appCtxLoaded.addListener((ov, oVal, nVal) -> { 123 | Platform.runLater(showMainAndCloseSplash); 124 | }); 125 | } 126 | } 127 | 128 | } 129 | 130 | /** 131 | * Show initial view. 132 | */ 133 | private void showInitialView() { 134 | final String stageStyle = applicationContext.getEnvironment().getProperty("javafx.stage.style"); 135 | if (stageStyle != null) { 136 | GUIState.getStage().initStyle(StageStyle.valueOf(stageStyle.toUpperCase())); 137 | } else { 138 | GUIState.getStage().initStyle(StageStyle.DECORATED); 139 | } 140 | // stage.hide(); 141 | 142 | showView(savedInitialView); 143 | } 144 | 145 | /** 146 | * Launch application view. 147 | * 148 | * @param ctx 149 | * the ctx 150 | */ 151 | private void launchApplicationView(final ConfigurableApplicationContext ctx) { 152 | AbstractJavaFxApplicationSupport.applicationContext = ctx; 153 | appCtxLoaded.set(true); 154 | } 155 | 156 | /** 157 | * Show view. 158 | * 159 | * @param newView 160 | * the new view 161 | */ 162 | public static void showView(final Class newView) { 163 | try { 164 | final AbstractFxmlView view = applicationContext.getBean(newView); 165 | 166 | if(GUIState.getScene() == null) { 167 | GUIState.setScene(new Scene(view.getView())); 168 | } 169 | else { 170 | GUIState.getScene().setRoot(view.getView()); 171 | } 172 | GUIState.getStage().setScene(GUIState.getScene()); 173 | 174 | applyEnvPropsToView(); 175 | 176 | GUIState.getStage().getIcons().addAll(icons); 177 | GUIState.getStage().show(); 178 | } catch(Throwable t) { 179 | LOGGER.error("Failed to load application: ", t); 180 | showErrorAlert(t); 181 | } 182 | } 183 | 184 | /** 185 | * Show error alert that close app. 186 | * 187 | * @param throwable 188 | * cause of error 189 | */ 190 | private static void showErrorAlert(Throwable throwable) { 191 | Alert alert = new Alert(AlertType.ERROR, "Oops! An unrecoverable error occurred.\n" + 192 | "Please contact your software vendor.\n\n" + 193 | "The application will stop now."); 194 | alert.showAndWait().ifPresent(response -> Platform.exit()); 195 | } 196 | /** 197 | * Apply env props to view. 198 | */ 199 | private static void applyEnvPropsToView() { 200 | PropertyReaderHelper.setIfPresent(applicationContext.getEnvironment(), "javafx.title", String.class, 201 | GUIState.getStage()::setTitle); 202 | 203 | PropertyReaderHelper.setIfPresent(applicationContext.getEnvironment(), "javafx.stage.width", Double.class, 204 | GUIState.getStage()::setWidth); 205 | 206 | PropertyReaderHelper.setIfPresent(applicationContext.getEnvironment(), "javafx.stage.height", Double.class, 207 | GUIState.getStage()::setHeight); 208 | 209 | PropertyReaderHelper.setIfPresent(applicationContext.getEnvironment(), "javafx.stage.resizable", Boolean.class, 210 | GUIState.getStage()::setResizable); 211 | } 212 | 213 | /* 214 | * (non-Javadoc) 215 | * 216 | * @see javafx.application.Application#stop() 217 | */ 218 | @Override 219 | public void stop() throws Exception { 220 | super.stop(); 221 | if (applicationContext != null) { 222 | applicationContext.close(); 223 | } // else: someone did it already 224 | } 225 | 226 | /** 227 | * Sets the title. Allows to overwrite values applied during construction at 228 | * a later time. 229 | * 230 | * @param title 231 | * the new title 232 | */ 233 | protected static void setTitle(final String title) { 234 | GUIState.getStage().setTitle(title); 235 | } 236 | 237 | /** 238 | * Launch app. 239 | * 240 | * @param appClass 241 | * the app class 242 | * @param view 243 | * the view 244 | * @param args 245 | * the args 246 | */ 247 | public static void launchApp(final Class appClass, 248 | final Class view, final String[] args) { 249 | launchApp(appClass, view, new SplashScreen(), args); 250 | } 251 | 252 | /** 253 | * Launch app. 254 | * 255 | * @param appClass 256 | * the app class 257 | * @param view 258 | * the view 259 | * @param splashScreen 260 | * the splash screen 261 | * @param args 262 | * the args 263 | */ 264 | public static void launchApp(final Class appClass, 265 | final Class view, final SplashScreen splashScreen, final String[] args) { 266 | savedInitialView = view; 267 | savedArgs = args; 268 | 269 | if (splashScreen != null) { 270 | AbstractJavaFxApplicationSupport.splashScreen = splashScreen; 271 | } else { 272 | AbstractJavaFxApplicationSupport.splashScreen = new SplashScreen(); 273 | } 274 | Application.launch(appClass, args); 275 | } 276 | 277 | } 278 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/jfxsupport/FXMLController.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.jfxsupport; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | * The annotation {@link FXMLController} is used to mark JavaFX controller 10 | * classes. Usage of this annotation happens besides registration of such within 11 | * fxml descriptors. 12 | * 13 | * @author Felix Roske 14 | */ 15 | @Component 16 | @Retention(RetentionPolicy.RUNTIME) 17 | public @interface FXMLController { 18 | 19 | } -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/jfxsupport/FXMLView.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.jfxsupport; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | * The annotation {@link FXMLView} indicates a class to be used in the context 10 | * of an JavaFX view. Such classes are used in combination with fxml markup 11 | * files. 12 | * 13 | * @author Felix Roske 14 | */ 15 | @Component 16 | @Retention(RetentionPolicy.RUNTIME) 17 | public @interface FXMLView { 18 | 19 | /** 20 | * Value refers to a relative path from where to load a certain fxml file. 21 | * 22 | * @return the relative file path of a views fxml file. 23 | */ 24 | String value() default ""; 25 | 26 | /** 27 | * Css files to be used together with this view. 28 | * 29 | * @return the string[] listing all css files. 30 | */ 31 | String[] css() default {}; 32 | 33 | /** 34 | * Resource bundle to be used with this view.. 35 | * 36 | * @return the string of such resource bundle. 37 | */ 38 | String bundle() default ""; 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/jfxsupport/GUIState.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.jfxsupport; 2 | 3 | import javafx.application.HostServices; 4 | import javafx.scene.Scene; 5 | import javafx.stage.Stage; 6 | 7 | /** 8 | * The enum {@link GUIState} stores Scene and Stage objects as singletons in 9 | * this VM. 10 | * 11 | * @author Felix Roske 12 | * @author Andreas Jay 13 | */ 14 | public enum GUIState { 15 | 16 | INSTANCE; 17 | private static Scene scene; 18 | 19 | private static Stage stage; 20 | 21 | private static String title; 22 | 23 | private static HostServices hostServices; 24 | 25 | public static String getTitle() { 26 | return title; 27 | } 28 | 29 | public static Scene getScene() { 30 | return scene; 31 | } 32 | 33 | public static Stage getStage() { 34 | return stage; 35 | } 36 | 37 | public static void setScene(final Scene scene) { 38 | GUIState.scene = scene; 39 | } 40 | 41 | public static void setStage(final Stage stage) { 42 | GUIState.stage = stage; 43 | } 44 | 45 | public static void setTitle(final String title) { 46 | GUIState.title = title; 47 | } 48 | 49 | public static HostServices getHostServices() { 50 | return hostServices; 51 | } 52 | 53 | public static void setHostServices(HostServices hostServices) { 54 | GUIState.hostServices = hostServices; 55 | } 56 | 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/jfxsupport/PropertyReaderHelper.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.jfxsupport; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.function.Consumer; 6 | 7 | import org.springframework.core.env.Environment; 8 | 9 | /** 10 | * The utility PropertyReaderHelper. 11 | * 12 | * @author Felix Roske 13 | * @author Andreas Jay 14 | */ 15 | public class PropertyReaderHelper { 16 | 17 | private PropertyReaderHelper() { 18 | } 19 | 20 | /** 21 | * Lookup in {@link Environment} a certain property or a list of properties. 22 | * 23 | * @param env 24 | * the {@link Environment} context from which to 25 | * @param propName 26 | * the name of the property to lookup from {@link Environment}. 27 | * @return the list 28 | */ 29 | public static List get(final Environment env, final String propName) { 30 | final List list = new ArrayList<>(); 31 | 32 | final String singleProp = env.getProperty(propName); 33 | if (singleProp != null) { 34 | list.add(singleProp); 35 | return list; 36 | } 37 | 38 | int counter = 0; 39 | String prop = env.getProperty(propName + "[" + counter + "]"); 40 | while (prop != null) { 41 | list.add(prop); 42 | counter++; 43 | prop = env.getProperty(propName + "[" + counter + "]"); 44 | } 45 | 46 | return list; 47 | } 48 | 49 | /** 50 | * Load from {@link Environment} a key with a given type. If sucj key is 51 | * present supply it in {@link Consumer}. 52 | * 53 | * @param 54 | * the generic type 55 | * @param env 56 | * the env 57 | * @param key 58 | * the key 59 | * @param type 60 | * the type 61 | * @param function 62 | * the function 63 | */ 64 | public static void setIfPresent(final Environment env, final String key, final Class type, 65 | final Consumer function) { 66 | final T value = env.getProperty(key, type); 67 | if (value != null) { 68 | function.accept(value); 69 | } 70 | } 71 | 72 | /** 73 | * Determine file path from package name creates from class package instance 74 | * the file path equivalent. The path will be prefixed and suffixed with a 75 | * slash. 76 | * 77 | * @return the path equivalent to a package structure. 78 | */ 79 | public static final String determineFilePathFromPackageName(final Class clazz) { 80 | return "/" + clazz.getPackage().getName().replace('.', '/') + "/"; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/jfxsupport/PrototypeController.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.jfxsupport; 2 | 3 | public interface PrototypeController { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/jfxsupport/SelectKeyComboBoxListener.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.jfxsupport; 2 | 3 | import javafx.collections.ObservableList; 4 | import javafx.event.EventHandler; 5 | import javafx.scene.control.ComboBox; 6 | import javafx.scene.control.ListView; 7 | import javafx.scene.control.skin.ComboBoxListViewSkin; 8 | import javafx.scene.input.KeyCode; 9 | import javafx.scene.input.KeyEvent; 10 | 11 | public class SelectKeyComboBoxListener implements EventHandler { 12 | 13 | private StringBuilder sb = new StringBuilder(); 14 | 15 | @Override 16 | @SuppressWarnings({ "rawtypes", "unchecked" }) 17 | public void handle(KeyEvent event) { 18 | sb.delete(0, sb.length()); // TODO: save last typed keys 19 | if (event.getCode() == KeyCode.DOWN || event.getCode() == KeyCode.UP || event.getCode() == KeyCode.TAB) { 20 | return; 21 | } else if (event.getCode() == KeyCode.BACK_SPACE && sb.length() > 0) { 22 | sb.deleteCharAt(sb.length() - 1); 23 | } else { 24 | sb.append(event.getText()); 25 | } 26 | 27 | if (sb.length() == 0) { 28 | return; 29 | } 30 | 31 | ComboBox comboBox = ((ComboBox) event.getSource()); 32 | boolean found = false; 33 | ObservableList items = comboBox.getItems(); 34 | for (int i = 0; i < items.size(); i++) { 35 | String strValue = comboBox.getConverter().toString(items.get(i)); 36 | if (strValue.toLowerCase().startsWith(sb.toString().toLowerCase())) { 37 | comboBox.getSelectionModel().select(i); 38 | ListView lv = (ListView) ((ComboBoxListViewSkin) comboBox.getSkin()).getPopupContent(); 39 | lv.scrollTo(lv.getSelectionModel().getSelectedIndex()); 40 | found = true; 41 | break; 42 | } 43 | } 44 | 45 | if (!found && sb.length() > 0) { 46 | sb.deleteCharAt(sb.length() - 1); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/jfxsupport/SplashScreen.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.jfxsupport; 2 | 3 | import javafx.scene.Parent; 4 | import javafx.scene.control.ProgressBar; 5 | import javafx.scene.image.ImageView; 6 | import javafx.scene.layout.VBox; 7 | 8 | /** 9 | * A default standard splash pane implementation Subclass it and override it's 10 | * methods to customize with your own behavior. 11 | * 12 | * @author Felix Roske 13 | * @author Andreas Jay 14 | */ 15 | public class SplashScreen { 16 | 17 | /** The path to the default imager absolute to classpath root. */ 18 | private static String DEFAULT_IMAGE = "/splash/javafx.png"; 19 | 20 | /** 21 | * Override this to create your own splash pane parent node. 22 | * 23 | * @return A standard image 24 | */ 25 | public Parent getParent() { 26 | final ImageView imageView = new ImageView(getClass().getResource(getImagePath()).toExternalForm()); 27 | final ProgressBar splashProgressBar = new ProgressBar(); 28 | splashProgressBar.setPrefWidth(imageView.getImage().getWidth()); 29 | 30 | final VBox vbox = new VBox(); 31 | vbox.getChildren().addAll(imageView, splashProgressBar); 32 | 33 | return vbox; 34 | } 35 | 36 | /** 37 | * Customize if the splash screen should be visible at all. 38 | * 39 | * @return true by default 40 | */ 41 | public boolean visible() { 42 | return true; 43 | } 44 | 45 | /** 46 | * Use your own splash image instead of the default one. 47 | * 48 | * @return "/splash/javafx.png" 49 | */ 50 | public String getImagePath() { 51 | return DEFAULT_IMAGE; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/model/Customer.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.model; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.Entity; 5 | 6 | @Entity 7 | public class Customer extends SimpleEntity { 8 | 9 | @Column(nullable = false) 10 | private String firstname; 11 | 12 | @Column(nullable = false) 13 | private String lastname; 14 | 15 | @Column 16 | private String address; 17 | 18 | @Column 19 | private String email; 20 | 21 | public String getFirstname() { 22 | return firstname; 23 | } 24 | 25 | public void setFirstname(String firstname) { 26 | this.firstname = firstname; 27 | } 28 | 29 | public String getLastname() { 30 | return lastname; 31 | } 32 | 33 | public void setLastname(String lastname) { 34 | this.lastname = lastname; 35 | } 36 | 37 | public String getAddress() { 38 | return address; 39 | } 40 | 41 | public void setAddress(String address) { 42 | this.address = address; 43 | } 44 | 45 | public String getEmail() { 46 | return email; 47 | } 48 | 49 | public void setEmail(String email) { 50 | this.email = email; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/model/MenuItem.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.model; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.Entity; 5 | import javax.persistence.Table; 6 | 7 | @Entity 8 | @Table(name = "menuitem") 9 | public class MenuItem extends SimpleEntity { 10 | 11 | @Column(nullable = false) 12 | private Long parent; 13 | 14 | @Column(nullable = false) 15 | private String key; 16 | 17 | @Column(nullable = false) 18 | private String value; 19 | 20 | @Column 21 | private String target; 22 | 23 | @Column 24 | private String service; 25 | 26 | @Column 27 | private String gridDef; 28 | 29 | @Column 30 | private String tooltip; 31 | 32 | @Column 33 | private String image; 34 | 35 | @Column 36 | private Boolean expanded; 37 | 38 | public Long getParent() { 39 | return parent; 40 | } 41 | 42 | public void setParent(Long parent) { 43 | this.parent = parent; 44 | } 45 | 46 | public String getKey() { 47 | return key; 48 | } 49 | 50 | public void setKey(String key) { 51 | this.key = key; 52 | } 53 | 54 | public String getValue() { 55 | return value; 56 | } 57 | 58 | public void setValue(String value) { 59 | this.value = value; 60 | } 61 | 62 | public String getTarget() { 63 | return target; 64 | } 65 | 66 | public void setTarget(String target) { 67 | this.target = target; 68 | } 69 | 70 | public String getService() { 71 | return service; 72 | } 73 | 74 | public void setService(String service) { 75 | this.service = service; 76 | } 77 | 78 | public String getGridDef() { 79 | return gridDef; 80 | } 81 | 82 | public void setGridDef(String gridDef) { 83 | this.gridDef = gridDef; 84 | } 85 | 86 | public String getTooltip() { 87 | return tooltip; 88 | } 89 | 90 | public void setTooltip(String tooltip) { 91 | this.tooltip = tooltip; 92 | } 93 | 94 | public String getImage() { 95 | return image; 96 | } 97 | 98 | public void setImage(String image) { 99 | this.image = image; 100 | } 101 | 102 | public Boolean getExpanded() { 103 | return expanded; 104 | } 105 | 106 | public void setExpanded(Boolean expanded) { 107 | this.expanded = expanded; 108 | } 109 | 110 | @Override 111 | public String toString() { 112 | return this.value; 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/model/SimpleEntity.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.model; 2 | 3 | import javax.persistence.GeneratedValue; 4 | import javax.persistence.GenerationType; 5 | import javax.persistence.Id; 6 | import javax.persistence.MappedSuperclass; 7 | import com.skynet.javafx.constants.CommonConstants; 8 | 9 | @MappedSuperclass 10 | public abstract class SimpleEntity { 11 | 12 | protected static final long serialVersionUID = CommonConstants.SERIAL_VERSION_UID; 13 | 14 | @Id 15 | @GeneratedValue(strategy = GenerationType.IDENTITY) 16 | private Long id; 17 | 18 | protected SimpleEntity() { 19 | super(); 20 | } 21 | 22 | public SimpleEntity(Long id) { 23 | this.id = id; 24 | } 25 | 26 | public Long getId() { 27 | return id; 28 | } 29 | 30 | protected void setId(Long id) { 31 | this.id = id; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/repository/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.repository; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | import com.skynet.javafx.model.Customer; 5 | 6 | public interface CustomerRepository extends CrudRepository { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/repository/MenuItemRepository.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.repository; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.data.repository.CrudRepository; 6 | import com.skynet.javafx.model.MenuItem; 7 | 8 | public interface MenuItemRepository extends CrudRepository { 9 | 10 | public List findByParent(Long parent); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/service/CustomerService.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.service; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | import com.skynet.javafx.model.Customer; 10 | import com.skynet.javafx.repository.CustomerRepository; 11 | 12 | @Service 13 | public class CustomerService implements FrameService { 14 | 15 | private static final Logger logger = LoggerFactory.getLogger(CustomerService.class); 16 | 17 | @Autowired 18 | CustomerRepository customerRepository; 19 | 20 | @Override 21 | public List getData() { 22 | Iterable it = customerRepository.findAll(); 23 | List result = new ArrayList<>(); 24 | it.forEach(result::add); 25 | return result; 26 | } 27 | 28 | @Override 29 | public void delete(Long id) { 30 | logger.debug("deleting customer with id: {}", id); 31 | customerRepository.deleteById(id); 32 | } 33 | 34 | public void save(Customer customer) { 35 | logger.debug("Saving customer: {}", customer); 36 | customerRepository.save(customer); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/service/FrameService.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.service; 2 | 3 | import java.util.List; 4 | import com.skynet.javafx.model.SimpleEntity; 5 | 6 | public interface FrameService { 7 | 8 | public List getData(); 9 | 10 | public void delete(Long id); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/service/MenuItemService.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.service; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Optional; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Service; 11 | import com.skynet.javafx.model.MenuItem; 12 | import com.skynet.javafx.repository.MenuItemRepository; 13 | 14 | @Service 15 | public class MenuItemService { 16 | 17 | private static final Logger logger = LoggerFactory.getLogger(MenuItemService.class); 18 | 19 | @Autowired 20 | MenuItemRepository menuItemRepository; 21 | 22 | public MenuItem getMenuItemRoot() { 23 | logger.info("Getting menu item root"); 24 | Optional root = menuItemRepository.findById(0l); 25 | return root.get(); 26 | } 27 | 28 | public List getAllMenuItems() { 29 | logger.info("Getting all menu items"); 30 | Iterable it = menuItemRepository.findAll(); 31 | List result = new ArrayList<>(); 32 | it.forEach(result::add); 33 | return result; 34 | } 35 | 36 | public List getMenuItemsByParent(Long parent) { 37 | logger.info("Getting menu items by parent {}", parent); 38 | Iterable it = menuItemRepository.findByParent(parent); 39 | List result = new ArrayList<>(); 40 | it.forEach(result::add); 41 | return result; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/views/CustomerView.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.views; 2 | 3 | import org.springframework.context.annotation.Scope; 4 | import com.skynet.javafx.jfxsupport.AbstractFxmlView; 5 | import com.skynet.javafx.jfxsupport.FXMLView; 6 | 7 | @FXMLView("/fxml/customer.fxml") 8 | @Scope("prototype") 9 | public class CustomerView extends AbstractFxmlView { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/views/FrameGridView.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.views; 2 | 3 | import org.springframework.context.annotation.Scope; 4 | import com.skynet.javafx.jfxsupport.AbstractFxmlView; 5 | import com.skynet.javafx.jfxsupport.FXMLView; 6 | 7 | @FXMLView("/fxml/framegrid.fxml") 8 | @Scope("prototype") 9 | public class FrameGridView extends AbstractFxmlView { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/views/MainView.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.views; 2 | 3 | import com.skynet.javafx.jfxsupport.AbstractFxmlView; 4 | import com.skynet.javafx.jfxsupport.FXMLView; 5 | 6 | @FXMLView("/fxml/main.fxml") 7 | public class MainView extends AbstractFxmlView { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/views/def/CustomerGridDef.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.views.def; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import org.springframework.stereotype.Component; 6 | import com.skynet.javafx.views.CustomerView; 7 | 8 | @Component 9 | public class CustomerGridDef implements FrameGridDef { 10 | 11 | public static String COLUMN_NAMES[] = 12 | { "Id", "Firstname", "Lastname", "Address", "Email"}; 13 | public static String COLUMN_DATA_NAMES[] = 14 | { "id", "firstname", "lastname", "address", "email" }; 15 | public static Integer COLUMN_SIZES[] = { 40, 200, 200, 200, 200 }; 16 | public static String TITLE_POPUPS = "Customer"; 17 | 18 | @Override 19 | public List getColumnNames() { 20 | return Arrays.asList(COLUMN_NAMES); 21 | } 22 | 23 | @Override 24 | public List getColumnSizes() { 25 | return Arrays.asList(COLUMN_SIZES); 26 | } 27 | 28 | @Override 29 | public List getColumnDataName() { 30 | return Arrays.asList(COLUMN_DATA_NAMES); 31 | } 32 | 33 | @Override 34 | public Class getCreateView() { 35 | return CustomerView.class; 36 | } 37 | 38 | @Override 39 | public String getTitlePopups() { 40 | return TITLE_POPUPS; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/skynet/javafx/views/def/FrameGridDef.java: -------------------------------------------------------------------------------- 1 | package com.skynet.javafx.views.def; 2 | 3 | import java.util.List; 4 | 5 | public interface FrameGridDef { 6 | 7 | public List getColumnNames(); 8 | 9 | public List getColumnDataName(); 10 | 11 | public List getColumnSizes(); 12 | 13 | public Class getCreateView(); 14 | 15 | public String getTitlePopups(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/resources/profiles/common/db/migration/V001__create_tables.sql: -------------------------------------------------------------------------------- 1 | create table menuitem ( 2 | id bigint not null, 3 | parent bigint not null, 4 | key varchar(255), 5 | value varchar(255), 6 | target varchar(255), 7 | service varchar(255), 8 | grid_def varchar(255), 9 | tooltip varchar(255), 10 | image varchar(255), 11 | expanded boolean, 12 | last_update datetime, 13 | PRIMARY KEY (id) 14 | ); 15 | 16 | create table customer ( 17 | id bigint auto_increment not null, 18 | firstname varchar(255), 19 | lastname varchar(255), 20 | address varchar(255), 21 | email varchar(255), 22 | PRIMARY KEY (id) 23 | ); -------------------------------------------------------------------------------- /src/main/resources/profiles/common/fxml/buttonbar.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |