Created
May 11, 2014 01:31
-
-
Save james-d/2cfba7a9edf6c00ac631 to your computer and use it in GitHub Desktop.
JavaFX client code for the Laboratory Information Management System demo. The key here is that the controller references a client-side data accessor, which uses a Jersey client to communicate with the web-based REST service.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package edu.marshall.genomics.lims.client; | |
import static javax.ws.rs.core.MediaType.APPLICATION_JSON; | |
import java.util.ArrayList; | |
import java.util.Iterator; | |
import java.util.List; | |
import javax.ws.rs.client.Client; | |
import javax.ws.rs.client.ClientBuilder; | |
import javax.ws.rs.client.Entity; | |
import javax.ws.rs.core.GenericType; | |
import org.glassfish.jersey.client.ClientConfig; | |
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; | |
import edu.marshall.genomics.lims.entities.Investigator; | |
import edu.marshall.genomics.lims.entities.Project; | |
import edu.marshall.genomics.lims.entities.Sample; | |
public class DataAccessor { | |
private final Client restClient ; | |
private final String baseUrl ; | |
public DataAccessor(String baseUrl) { | |
this.baseUrl = baseUrl ; | |
ClientConfig config = new ClientConfig(); | |
config.register(new JacksonJsonProvider()); | |
this.restClient = ClientBuilder.newClient(config); | |
} | |
public List<Investigator> getAllInvestigators() { | |
return restClient.target(baseUrl) | |
.path("/investigators") | |
.request(APPLICATION_JSON) | |
.get(new GenericType<List<Investigator>>(){}); | |
} | |
public Investigator getInvestigatorByEmail(String email) { | |
return restClient.target(baseUrl) | |
.path("/investigators/"+email) | |
.request(APPLICATION_JSON) | |
.get(Investigator.class); | |
} | |
public List<Project> getAllProjects() { | |
return restClient.target(baseUrl) | |
.path("/projects") | |
.request(APPLICATION_JSON) | |
.get(new GenericType<List<Project>>(){}); | |
} | |
public List<Project> getProjectsByInvestigator(Investigator investigator) { | |
String path = "/projects/"+investigator.getId(); | |
return restClient.target(baseUrl) | |
.path(path) | |
.request(APPLICATION_JSON) | |
.get(new GenericType<List<Project>>(){}); | |
} | |
public List<Sample> getSamplesByProject(Project project) { | |
return restClient.target(baseUrl) | |
.path("/samples/"+project.getId()) | |
.request(APPLICATION_JSON) | |
.get(new GenericType<List<Sample>>(){}); | |
} | |
public void addInvestigators(List<Investigator> investigators) { | |
restClient.target(baseUrl) | |
.path("/investigators") | |
.request() | |
.post(Entity.entity(investigators, APPLICATION_JSON), new GenericType<ArrayList<Integer>>(){}); | |
} | |
public void updateInvestigator(Investigator investigator) { | |
restClient.target(baseUrl) | |
.path("/investigators") | |
.request() | |
.put(Entity.entity(investigator, APPLICATION_JSON)); | |
} | |
public List<Integer> addProjects(List<Project> projects) { | |
List<Integer> assignedIds = restClient.target(baseUrl) | |
.path("/projects") | |
.request(APPLICATION_JSON) | |
.post(Entity.entity(projects, APPLICATION_JSON), new GenericType<ArrayList<Integer>>(){}); | |
return assignedIds ; | |
} | |
public void updateProject(Project project) { | |
restClient.target(baseUrl) | |
.path("/projects") | |
.request() | |
.put(Entity.entity(project, APPLICATION_JSON)); | |
} | |
public void addSamples(List<Sample> samples) { | |
List<Integer> assignedIds = restClient.target(baseUrl) | |
.path("/samples") | |
.request(APPLICATION_JSON) | |
.post(Entity.entity(samples, APPLICATION_JSON), new GenericType<ArrayList<Integer>>(){}); | |
Iterator<Sample> sampleIterator = samples.iterator(); | |
for (int id : assignedIds) | |
sampleIterator.next().setId(id); | |
} | |
public void updateSample(Sample sample) { | |
restClient.target(baseUrl) | |
.path("/samples") | |
.request() | |
.put(Entity.entity(sample, APPLICATION_JSON)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package edu.marshall.genomics.lims.client; | |
import javafx.collections.FXCollections; | |
import javafx.collections.ObservableList; | |
import edu.marshall.genomics.lims.entities.Investigator; | |
import edu.marshall.genomics.lims.entities.Project; | |
import edu.marshall.genomics.lims.entities.Sample; | |
public class DataModel { | |
private final ObservableList<Investigator> investigators ; | |
private final ObservableList<Project> displayedProjects ; | |
private final ObservableList<Sample> displayedSamples ; | |
public DataModel() { | |
investigators = FXCollections.observableArrayList(); | |
displayedProjects = FXCollections.observableArrayList(); | |
displayedSamples = FXCollections.observableArrayList(); | |
} | |
public ObservableList<Investigator> getInvestigators() { | |
return investigators; | |
} | |
public ObservableList<Project> getDisplayedProjects() { | |
return displayedProjects; | |
} | |
public ObservableList<Sample> getDisplayedSamples() { | |
return displayedSamples; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<?import javafx.scene.layout.GridPane?> | |
<?import javafx.scene.control.Label?> | |
<?import javafx.scene.control.TextField?> | |
<?import javafx.scene.layout.ColumnConstraints?> | |
<GridPane xmlns:fx="http://javafx.com/fxml" | |
fx:controller="edu.marshall.genomics.lims.client.InvestigatorDetailController" | |
hgap="10" vgap="5"> | |
<Label text="First name:" GridPane.rowIndex="0" | |
GridPane.columnIndex="0" /> | |
<Label text="Last name:" GridPane.rowIndex="1" | |
GridPane.columnIndex="0" /> | |
<Label text="Email:" GridPane.rowIndex="2" GridPane.columnIndex="0" /> | |
<Label text="Institution:" GridPane.rowIndex="3" | |
GridPane.columnIndex="0" /> | |
<Label fx:id="firstNameLabel" GridPane.rowIndex="0" | |
GridPane.columnIndex="1" /> | |
<Label fx:id="lastNameLabel" GridPane.rowIndex="1" | |
GridPane.columnIndex="1" /> | |
<Label fx:id="emailLabel" GridPane.rowIndex="2" | |
GridPane.columnIndex="1" /> | |
<Label fx:id="institutionLabel" GridPane.rowIndex="3" | |
GridPane.columnIndex="1" /> | |
<TextField fx:id="firstNameTextField" GridPane.rowIndex="0" | |
GridPane.columnIndex="1" /> | |
<TextField fx:id="lastNameTextField" GridPane.rowIndex="1" | |
GridPane.columnIndex="1" /> | |
<TextField fx:id="emailTextField" GridPane.rowIndex="2" | |
GridPane.columnIndex="1" /> | |
<TextField fx:id="institutionTextField" GridPane.rowIndex="3" | |
GridPane.columnIndex="1" /> | |
<columnConstraints> | |
<ColumnConstraints hgrow="NEVER" halignment="RIGHT" /> | |
<ColumnConstraints hgrow="ALWAYS" halignment="LEFT" /> | |
</columnConstraints> | |
</GridPane> | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package edu.marshall.genomics.lims.client; | |
import org.fxmisc.easybind.EasyBind; | |
import javafx.beans.property.BooleanProperty; | |
import javafx.beans.property.ObjectProperty; | |
import javafx.beans.property.SimpleBooleanProperty; | |
import javafx.beans.property.SimpleObjectProperty; | |
import javafx.fxml.FXML; | |
import javafx.scene.control.Label; | |
import javafx.scene.control.TextField; | |
import edu.marshall.genomics.lims.entities.Investigator; | |
public class InvestigatorDetailController { | |
@FXML | |
private Label firstNameLabel ; | |
@FXML | |
private Label lastNameLabel ; | |
@FXML | |
private Label emailLabel ; | |
@FXML | |
private Label institutionLabel ; | |
@FXML | |
private TextField firstNameTextField ; | |
@FXML | |
private TextField lastNameTextField ; | |
@FXML | |
private TextField emailTextField ; | |
@FXML | |
private TextField institutionTextField ; | |
private final BooleanProperty editMode = new SimpleBooleanProperty(this, "editMode"); | |
private final ObjectProperty<Investigator> investigator = new SimpleObjectProperty<>(); | |
public Investigator getInvestigator() { | |
return investigator.get(); | |
} | |
public ObjectProperty<Investigator> investigatorProperty() { | |
return investigator; | |
} | |
public void initialize() { | |
firstNameLabel.visibleProperty().bind(editMode.not()); | |
lastNameLabel.visibleProperty().bind(editMode.not()); | |
emailLabel.visibleProperty().bind(editMode.not()); | |
institutionLabel.visibleProperty().bind(editMode.not()); | |
firstNameTextField.visibleProperty().bind(editMode); | |
lastNameTextField.visibleProperty().bind(editMode); | |
emailTextField.visibleProperty().bind(editMode); | |
institutionTextField.visibleProperty().bind(editMode); | |
firstNameLabel.textProperty().bind(EasyBind.select(investigator).selectObject(Investigator::firstNameProperty)); | |
lastNameLabel.textProperty().bind(EasyBind.select(investigator).selectObject(Investigator::lastNameProperty)); | |
emailLabel.textProperty().bind(EasyBind.select(investigator).selectObject(Investigator::emailProperty)); | |
institutionLabel.textProperty().bind(EasyBind.select(investigator).selectObject(Investigator::institutionProperty)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package edu.marshall.genomics.lims.client; | |
import java.lang.reflect.Constructor; | |
import javafx.application.Application; | |
import javafx.fxml.FXMLLoader; | |
import javafx.scene.Parent; | |
import javafx.scene.Scene; | |
import javafx.stage.Stage; | |
public class Lims extends Application { | |
@Override | |
public void start(Stage primaryStage) { | |
try { | |
DataModel dataModel = new DataModel(); | |
FXMLLoader loader = new FXMLLoader(getClass().getResource("Main.fxml")); | |
loader.setControllerFactory(type -> { | |
try { | |
for (Constructor<?> constructor : type.getConstructors()) { | |
if (constructor.getParameterCount() == 1 && constructor.getParameterTypes()[0] == DataModel.class) { | |
return constructor.newInstance(dataModel); | |
} | |
} | |
return type.newInstance() ; | |
} catch (Exception e) { | |
e.printStackTrace(); | |
return null ; | |
} | |
}); | |
Parent root = loader.<Parent>load(); | |
Scene scene = new Scene(root, 800, 600); | |
primaryStage.setScene(scene); | |
primaryStage.show(); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
public static void main(String[] args) { | |
launch(args); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<?import javafx.scene.layout.BorderPane?> | |
<?import javafx.scene.layout.VBox?> | |
<?import javafx.scene.control.ListView?> | |
<?import javafx.scene.control.TextField?> | |
<?import javafx.scene.control.Button?> | |
<?import javafx.scene.control.Label?> | |
<?import java.net.URL ?> | |
<?import javafx.geometry.Insets?> | |
<BorderPane xmlns:fx="http://javafx.com/fxml" | |
fx:controller="edu.marshall.genomics.lims.client.MainController"> | |
<left> | |
<VBox spacing="5" minWidth="350"> | |
<BorderPane.margin> | |
<Insets top="5" bottom="5" left="5" right="5" /> | |
</BorderPane.margin> | |
<Label text="Investigators" styleClass="title" /> | |
<ListView fx:id="investigatorList" /> | |
<fx:include source="InvestigatorDetail.fxml" fx:id="investigatorDetail" /> | |
</VBox> | |
</left> | |
<center> | |
<BorderPane> | |
<left> | |
<VBox spacing="5"> | |
<BorderPane.margin> | |
<Insets top="5" bottom="5" left="5" right="5" /> | |
</BorderPane.margin> | |
<Label text="Projects" styleClass="title" /> | |
<ListView fx:id="projectList" /> | |
<TextField fx:id="newProjectTextField" onAction="#addProject" /> | |
<Button fx:id="newProjectButton" onAction="#addProject" | |
text="Add Project" /> | |
</VBox> | |
</left> | |
<center> | |
<VBox spacing="5"> | |
<BorderPane.margin> | |
<Insets top="5" bottom="5" left="5" right="5" /> | |
</BorderPane.margin> | |
<Label text="Samples" styleClass="title" /> | |
<ListView fx:id="sampleList" /> | |
<TextField fx:id="newSampleTextField" onAction="#addSample" /> | |
<Button fx:id="newSampleButton" onAction="#addSample" text="Add Sample" /> | |
</VBox> | |
</center> | |
</BorderPane> | |
</center> | |
<stylesheets> | |
<URL value="@lims.css" /> | |
</stylesheets> | |
</BorderPane> | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package edu.marshall.genomics.lims.client; | |
import java.util.Collections; | |
import java.util.List; | |
import java.util.concurrent.ExecutorService; | |
import java.util.concurrent.Executors; | |
import java.util.function.Function; | |
import java.util.function.Supplier; | |
import javafx.beans.binding.Bindings; | |
import javafx.beans.binding.BooleanBinding; | |
import javafx.beans.value.ObservableValue; | |
import javafx.collections.ObservableList; | |
import javafx.concurrent.Task; | |
import javafx.fxml.FXML; | |
import javafx.scene.control.Button; | |
import javafx.scene.control.ListCell; | |
import javafx.scene.control.ListView; | |
import javafx.scene.control.TextField; | |
import org.fxmisc.easybind.EasyBind; | |
import edu.marshall.genomics.lims.entities.Investigator; | |
import edu.marshall.genomics.lims.entities.Project; | |
import edu.marshall.genomics.lims.entities.Sample; | |
public class MainController { | |
private final DataModel dataModel; | |
private final DataAccessor dataAccessor; | |
private final ExecutorService exec; | |
@FXML | |
private ListView<Investigator> investigatorList; | |
@FXML | |
private ListView<Project> projectList; | |
@FXML | |
private ListView<Sample> sampleList; | |
@FXML | |
private TextField newProjectTextField; | |
@FXML | |
private TextField newSampleTextField; | |
@FXML | |
private Button newProjectButton; | |
@FXML | |
private Button newSampleButton; | |
@FXML | |
private InvestigatorDetailController investigatorDetailController; | |
public MainController(DataModel dataModel) { | |
this.dataModel = dataModel; | |
this.dataAccessor = new DataAccessor("http://localhost:8080/LIMS-web/"); | |
this.exec = Executors.newSingleThreadExecutor(runnable -> { | |
Thread thread = new Thread(runnable); | |
thread.setDaemon(true); | |
return thread; | |
}); | |
} | |
public void initialize() { | |
bindListsToDataModel(); | |
setUpButtonAndTextFieldDisableBindings(); | |
setupListCellFactories(); | |
loadInvestigators(); | |
investigatorList.getSelectionModel().selectedItemProperty() | |
.addListener((obs, oldInv, newInv) -> { | |
dataModel.getDisplayedProjects().clear(); | |
if (newInv != null) { | |
loadProjects(newInv); | |
} | |
}); | |
projectList.getSelectionModel().selectedItemProperty() | |
.addListener((obs, oldProject, newProject) -> { | |
dataModel.getDisplayedSamples().clear(); | |
if (newProject != null) { | |
loadSamples(newProject); | |
} | |
}); | |
investigatorDetailController.investigatorProperty().bind( | |
investigatorList.getSelectionModel().selectedItemProperty()); | |
} | |
private void bindListsToDataModel() { | |
investigatorList.setItems(dataModel.getInvestigators()); | |
projectList.setItems(dataModel.getDisplayedProjects()); | |
sampleList.setItems(dataModel.getDisplayedSamples()); | |
} | |
private void setUpButtonAndTextFieldDisableBindings() { | |
disableNodesOnEmptySelectionOrTextField(investigatorList, | |
newProjectTextField, newProjectButton); | |
disableNodesOnEmptySelectionOrTextField(projectList, | |
newSampleTextField, newSampleButton); | |
} | |
private void disableNodesOnEmptySelectionOrTextField(ListView<?> listView, | |
TextField textField, Button button) { | |
BooleanBinding emptySelection = Bindings.isNull(listView | |
.getSelectionModel().selectedItemProperty()); | |
textField.disableProperty().bind(emptySelection); | |
button.disableProperty().bind( | |
emptySelection.or(Bindings.isEmpty(textField.textProperty()))); | |
} | |
private void setupListCellFactories() { | |
setUpListCellFactory( | |
investigatorList, | |
investigator -> EasyBind.combine( | |
investigator.firstNameProperty(), | |
investigator.lastNameProperty(), | |
(s1, s2) -> s1 + " " + s2)); | |
setUpListCellFactory(projectList, Project::titleProperty); | |
setUpListCellFactory(sampleList, Sample::sampleIdProperty); | |
} | |
private <T> void setUpListCellFactory(ListView<T> listView, | |
Function<T, ObservableValue<String>> renderFunction) { | |
listView.setCellFactory(lv -> | |
{ | |
ListCell<T> cell = new ListCell<>(); | |
cell.textProperty().bind( | |
EasyBind.select(cell.itemProperty()) | |
.selectObject(renderFunction)); | |
return cell; | |
}); | |
} | |
private void loadInvestigators() { | |
loadEntities(dataAccessor::getAllInvestigators, | |
dataModel.getInvestigators()); | |
} | |
private void loadProjects(Investigator investigator) { | |
loadEntities( | |
() -> dataAccessor.getProjectsByInvestigator(investigator), | |
dataModel.getDisplayedProjects()); | |
} | |
private void loadSamples(Project project) { | |
loadEntities(() -> dataAccessor.getSamplesByProject(project), | |
dataModel.getDisplayedSamples()); | |
} | |
private <T> void loadEntities(Supplier<List<T>> supplier, | |
ObservableList<T> receivingList) { | |
Task<List<T>> task = new Task<List<T>>() { | |
@Override | |
protected List<T> call() throws Exception { | |
return supplier.get(); | |
} | |
}; | |
task.setOnSucceeded(event -> receivingList.setAll(task.getValue())); | |
task.setOnFailed(event -> handleException(task)); | |
exec.submit(task); | |
} | |
private void handleException(Task<?> failedTask) { | |
// TODO | |
failedTask.getException().printStackTrace(); | |
} | |
@FXML | |
public void addProject() { | |
Project project = new Project(); | |
project.setTitle(newProjectTextField.getText()); | |
Investigator investigator = | |
investigatorList.getSelectionModel().getSelectedItem(); | |
project.setInvestigator(investigator); | |
Task<Void> addProjectTask = new Task<Void>() { | |
@Override | |
protected Void call() throws Exception { | |
dataAccessor.addProjects(Collections.singletonList(project)); | |
return null; | |
} | |
}; | |
addProjectTask.setOnSucceeded(event -> loadProjects(investigator)); | |
addProjectTask.setOnFailed(event -> handleException(addProjectTask)); | |
exec.submit(addProjectTask); | |
} | |
@FXML | |
public void addSample() { | |
Sample sample = new Sample(); | |
sample.setSampleId(newSampleTextField.getText()); | |
Project project = projectList.getSelectionModel().getSelectedItem(); | |
sample.setProject(project); | |
Task<Void> addSampleTask = new Task<Void>() { | |
@Override | |
protected Void call() throws Exception { | |
dataAccessor.addSamples(Collections.singletonList(sample)); | |
return null; | |
} | |
}; | |
addSampleTask.setOnSucceeded(event -> loadSamples(project)); | |
addSampleTask.setOnFailed(event -> handleException(addSampleTask)); | |
exec.submit(addSampleTask); | |
newSampleTextField.setText(""); | |
newSampleTextField.requestFocus(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment