diff --git a/app/assets/images/javafx/enahanced-myshapes-application.png b/app/assets/images/javafx/enahanced-myshapes-application.png
new file mode 100644
index 0000000..8271223
Binary files /dev/null and b/app/assets/images/javafx/enahanced-myshapes-application.png differ
diff --git a/app/assets/images/javafx/javafx-coordinates.png b/app/assets/images/javafx/javafx-coordinates.png
new file mode 100644
index 0000000..cf9449b
Binary files /dev/null and b/app/assets/images/javafx/javafx-coordinates.png differ
diff --git a/app/assets/images/javafx/myshapes-application.png b/app/assets/images/javafx/myshapes-application.png
new file mode 100644
index 0000000..7abddf2
Binary files /dev/null and b/app/assets/images/javafx/myshapes-application.png differ
diff --git a/app/assets/images/javafx/myshapes-fxml-css.png b/app/assets/images/javafx/myshapes-fxml-css.png
new file mode 100644
index 0000000..4ce7cb7
Binary files /dev/null and b/app/assets/images/javafx/myshapes-fxml-css.png differ
diff --git a/app/assets/images/javafx/myshapes-properties-change.png b/app/assets/images/javafx/myshapes-properties-change.png
new file mode 100644
index 0000000..7e8953d
Binary files /dev/null and b/app/assets/images/javafx/myshapes-properties-change.png differ
diff --git a/app/assets/images/javafx/myshapes-properties-fluent.png b/app/assets/images/javafx/myshapes-properties-fluent.png
new file mode 100644
index 0000000..1ee43b9
Binary files /dev/null and b/app/assets/images/javafx/myshapes-properties-fluent.png differ
diff --git a/app/assets/images/javafx/myshapes-properties-invalidation.png b/app/assets/images/javafx/myshapes-properties-invalidation.png
new file mode 100644
index 0000000..aeec43e
Binary files /dev/null and b/app/assets/images/javafx/myshapes-properties-invalidation.png differ
diff --git a/app/assets/images/javafx/myshapes-properties.png b/app/assets/images/javafx/myshapes-properties.png
new file mode 100644
index 0000000..a6d16da
Binary files /dev/null and b/app/assets/images/javafx/myshapes-properties.png differ
diff --git a/app/assets/images/javafx/myshapes-scene-graph.png b/app/assets/images/javafx/myshapes-scene-graph.png
new file mode 100644
index 0000000..bb7dc27
Binary files /dev/null and b/app/assets/images/javafx/myshapes-scene-graph.png differ
diff --git a/app/assets/images/javafx/person-ui-app.png b/app/assets/images/javafx/person-ui-app.png
new file mode 100644
index 0000000..1d03acc
Binary files /dev/null and b/app/assets/images/javafx/person-ui-app.png differ
diff --git a/app/assets/images/javafx/person-ui-file-struct.png b/app/assets/images/javafx/person-ui-file-struct.png
new file mode 100644
index 0000000..bfbb2ea
Binary files /dev/null and b/app/assets/images/javafx/person-ui-file-struct.png differ
diff --git a/app/assets/images/javafx/person-ui-scene-graph.png b/app/assets/images/javafx/person-ui-scene-graph.png
new file mode 100644
index 0000000..c219817
Binary files /dev/null and b/app/assets/images/javafx/person-ui-scene-graph.png differ
diff --git a/app/assets/images/javafx/rotate-transition-myshapes-application.png b/app/assets/images/javafx/rotate-transition-myshapes-application.png
new file mode 100644
index 0000000..dacfda5
Binary files /dev/null and b/app/assets/images/javafx/rotate-transition-myshapes-application.png differ
diff --git a/app/assets/images/javafx/scene-builder.png b/app/assets/images/javafx/scene-builder.png
new file mode 100644
index 0000000..2c5dea8
Binary files /dev/null and b/app/assets/images/javafx/scene-builder.png differ
diff --git a/app/data/authors.yaml b/app/data/authors.yaml
index 33afcb6..422ba03 100644
--- a/app/data/authors.yaml
+++ b/app/data/authors.yaml
@@ -69,3 +69,29 @@
a monthly newsletter that has been in publication since the end of 2000. In it, he explores
many useful tips and tricks that have enthralled tens of thousands of enthusiastic fans for
over two decades. He was one of the first [Java Champions](https://www.javachampions.org).
+
+- name: Paul Anderson
+ photo_url: https://asgteach.com/wp-content/uploads/2016/02/PaulAnderson-300x291.png
+ twitter: Paul_ASGTeach
+ website: https://asgteach.com/
+ description: |
+ Paul is a Java Champion and a member of the Oracle ACE Program. He is an experienced speaker,
+ making the technical aspects of software engineering fun and understandable.
+ He has conducted Technical Sessions and Tutorials at JavaOne, Devoxx, and NetBeans Day conferences
+ in San Francisco, Europe, and Latin America. Paul is also the author of LiveLesson videos on JavaFX
+ Programming and Java Reflection for Pearson Education. He has contributed articles to InformIT
+ and is the co-author of eight textbooks on software programming.
+
+- name: Gail C. Anderson
+ photo_url: https://asgteach.com/wp-content/uploads/2016/02/GailAnderson.png
+ twitter: gail_asgteach
+ website: https://asgteach.com/
+ description: |
+ Gail is a Java Champion and a member of the Oracle ACE Program. She is Director of Research
+ and founding member of the Anderson Software Group, a leading provider of training courses
+ in Java, JavaFX, Python, Go, Modern C++, and other programming languages.
+ Gail enjoys researching and writing about leading-edge Java technologies. Her current passion
+ includes JavaFX with GraalVM for cross-platform mobile applications. She is the co-author of
+ nine textbooks on software programming. Most recently, she is a contributing author to
+ The Definitive Guide to Modern Java Clients with JavaFX 17. Gail has presented at various
+ Java conferences and JUGS including Devoxx, DevNexus, JCrete, and Oracle Code/JavaOne worldwide.
diff --git a/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/00_javafx_overview.md b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/00_javafx_overview.md
new file mode 100644
index 0000000..86f753e
--- /dev/null
+++ b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/00_javafx_overview.md
@@ -0,0 +1,26 @@
+---
+id: javafx.fundamentals
+title: "JavaFX Fundamentals"
+slug: learn/javafx
+slug_history:
+ - javafx
+type: tutorial
+category: javafx
+category_order: 1
+group: rich-client-apps
+layout: learn/tutorial-group-top.html
+subheader_select: tutorials
+main_css_id: learn
+description: "Learn to create simple JavaFX applications."
+author: ["GailC.Anderson", "PaulAnderson"]
+byline: 'and is from The Definitive Guide to Modern Java Clients with JavaFX 17 graciously contributed by Apress.'
+---
+
+With the Java SDK and JavaFX installed on your system, let’s create some applications and explore the fundamentals of JavaFX. First, we’ll describe the basic structure of a JavaFX application along with selected features that make JavaFX a powerful choice for modern clients. We’ll show you how to create UIs that are appealing and responsive. We’ll look at FXML, the XML-based markup language that lets you define and configure your UI. We’ll also introduce Scene Builder, a stand-alone drag-and-drop utility for designing and configuring a JavaFX UI.
+
+To further refine or completely restyle your UI, JavaFX uses Cascading Style Sheets (CSS). We’ll show you several ways to use CSS with JavaFX.
+
+JavaFX properties provide a powerful binding mechanism. We’ll introduce JavaFX properties and binding. We’ll show why JavaFX observables and binding help create compact code that is less error prone than bulky listeners. We’ll also explore several layout controls and show you how easy it is to incorporate animation into your UI.
+
+We’ll finish the series with a sample application that implements a master-detail UI using JavaFX collections, an editable form, and buttons for typical database CRUD operations.
+This, then, is meant to give you a taste of what is possible with JavaFX and to provide the basics for exploring JavaFX even further throughout this book. Let’s begin!
\ No newline at end of file
diff --git a/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/01_app_structure.md b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/01_app_structure.md
new file mode 100644
index 0000000..965db2b
--- /dev/null
+++ b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/01_app_structure.md
@@ -0,0 +1,184 @@
+---
+id: javafx.fundamentals.structure
+title: JavaFX Application Basic Structure By Example
+slug: learn/javafx/structure
+type: tutorial-group
+group: rich-client-apps
+category: javafx
+layout: learn/tutorial-group.html
+subheader_select: tutorials
+main_css_id: learn
+toc:
+- JavaFX Stage and Scene Graph {stage-scene}
+- JavaFX Is Single-Threaded {single-threaded}
+- Hierarchical Node Structure {hierarchical-node-structure}
+- A Simple Shape Example {shape}
+- Color {color}
+- Text Is a Shape {text}
+- The JavaFX Coordinate System {coordinate-system}
+description: "Explore JavaFX application components and build a simple application."
+last_update: 2023-09-11
+author: ["GailC.Anderson", "PaulAnderson"]
+byline: 'and is from The Definitive Guide to Modern Java Clients with JavaFX 17 graciously contributed by Apress.'
+---
+
+
+## JavaFX Stage and Scene Graph
+
+A JavaFX application is controlled by the JavaFX platform, a runtime system that builds your application object and constructs the `JavaFX Application Thread`. To build a JavaFX application, you must extend the `JavaFX Application` class.
+The JavaFX runtime system controls the Application lifecycle and invokes the Application `start()` method.
+
+JavaFX uses a theater metaphor: the top-level container is the `Stage` and is constructed by the platform for you. In desktop applications, the `Stage` is the window. Its appearance depends on the host system and varies among Mac OS X, Windows, and Linux platforms.
+Normally, the window is decorated with controls that resize, minimize, and quit your application. It’s also possible to construct undecorated windows. You can specialize the Application class for other environments, too. For example, with the `Gluon Mobile Application` framework, your program extends Mobile Application, an application class specifically written for mobile devices.
+
+## JavaFX Is Single-Threaded
+
+You must always construct and modify the `Stage` and its scene objects on the `JavaFX Application Thread`. Note that JavaFX (like `Swing`) is a single-threaded UI model. For the JavaFX developer, this is mostly a straightforward restriction.
+As you create UI elements, respond to event handlers, manage dynamic content with animation, or make changes in the scene graph, work continues to execute on the JavaFX Application Thread.
+
+To keep the UI responsive, however, you should assign long-running work to background tasks in separate threads. In this case, work that modifies the UI must be separate from work being executed on a background thread.
+Fortunately, JavaFX has a well-developed concurrency API that helps developers assign long-running tasks to one or more separate threads. This keeps the UI thread responsive to user events.
+
+## Hierarchical Node Structure
+
+Continuing with the theater metaphor, the `Stage` holds a scene. The scene consists of JavaFX elements such as the root, which is the top scene element and contains what is called the scene graph.
+
+The scene graph is a strictly hierarchical structure of elements that visualize your application. These elements are called Nodes. A Node has exactly one parent (except the root node) and may contain other Nodes. Or, a Node can be a leaf node with no children. Nodes must be added to the scene graph in order to participate in the rendering of that scene.
+Furthermore, a Node may be added only once to a scene, unless it is first removed and then added somewhere else.
+
+Parent nodes in general manage their children by arranging them within the scene according to layout rules and any constraints you configure. JavaFX uses a two-dimensional coordinate system for 2D graphics with the origin at the upper-left corner of the scene,
+as shown in the figure below. Coordinate values on the x-axis increase to the right, and y-axis values increase as you move down the scene.
+
+[](/assets/images/javafx/javafx-coordinates.png)
+
+JavaFX also supports 3D graphics and represents the third dimension with z-axis values, providing depth.
+JavaFX has an absolute coordinate system, in addition to local coordinate systems that are relative to the parent. In each case, the coordinate system’s origin is the upper-left corner of the parent.
+In general, layout controls hide the complexities of component placement within the scene and manage the placement of its children for you. Component placement is based on the specific layout control and how you configure it.
+It’s also possible to nest layout controls. For example, you can place multiple VBox controls in an `HBox` or put an `AnchorPane` into one pane of a `SplitPane` control. Other parent nodes are more complex visual nodes, such as `TextField`, `TextArea`, and `Button`.
+These nodes have managed subparts. For example, `Button` includes a labeled text part and optional graphic. This graphic can be any node type but is typically an image or icon.
+
+Recall that leaf nodes have no child nodes. Examples include `Shape` (such as `Rectangle`, `Ellipse`, `Line`, `Path`, and `Text`) and `ImageView`, a node for rendering an image.
+
+Just a word of warning: you should be using a plain text editor to create and save this file. Using a word processor will not work.
+
+## A Simple Shape Example
+
+The picture below shows a simple JavaFX application called `MyShapes` that displays an ellipse and a text element centered in an application window. The appearance of this window varies depending on the underlying platform.
+When you resize the window, the visible elements will remain centered in the resized space. Even though this is a simple program, there’s much to learn here about JavaFX rendering, layout features, and nodes.
+
+[](/assets/images/javafx/myshapes-application.png)
+
+The source code for this application is in the `MyShapes` program. Class `MyShapes` is the main class and extends `Application`. The JavaFX runtime system instantiates `MyShapes` as well as the primary Stage, which it passes to the overridden `start()` method. The runtime system invokes the `start()` method for you.
+
+```java
+package org.modernclient;
+import javafx.application.Application;
+import javafx.scene.Scene;
+import javafx.scene.layout.StackPane;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Ellipse;
+import javafx.scene.text.Font;
+import javafx.scene.text.Text;
+import javafx.stage.Stage;
+public class MyShapes extends Application {
+ @Override
+ public void start(Stage stage) throws Exception {
+ // Create an Ellipse and set fill color
+ Ellipse ellipse = new Ellipse(110, 70);
+ ellipse.setFill(Color.LIGHTBLUE);
+ // Create a Text shape with font and size
+ Text text = new Text("My Shapes");
+ text.setFont(new Font("Arial Bold", 24));
+ StackPane stackPane = new StackPane();
+ stackPane.getChildren().addAll(ellipse, text);
+ Scene scene = new Scene(stackPane, 350, 230,
+ Color.LIGHTYELLOW);
+ stage.setTitle("MyShapes with JavaFX");
+ stage.setScene(scene);
+ stage.show();
+ }
+ public static void main(String[] args) {
+ launch(args);
+ }
+}
+```
+
+Note the import statements that reference packages in `javafx.application`, `javafx.scene`, and `javafx.stage`.
+
+---
+**NOTE**: Be sure to specify the correct package for any import statements. Some JavaFX classes, such as Rectangle, have the same class name as their AWT or Swing counterparts. All JavaFX classes are part of package `javafx`.
+
+---
+
+This program creates several nodes and adds them to a `StackPane` layout container. The program also creates the scene, configures the stage, and shows the stage. Let’s look at these steps in detail.
+
+First, we create an `Ellipse` shape, providing a width and height in pixels. Since `Ellipse` extends `Shape`, we can also configure any `Shape` property. This includes fill, which lets you specify an interior paint value.
+
+## Color
+
+A `Shape`’s fill property can be a JavaFX color, a linear gradient, a radial gradient, or an image. Let’s briefly discuss color. You can specify colors in JavaFX several ways.
+Here, we set the `Ellipse` fill property to `Color.LIGHTBLUE`.
+
+```java
+// Create an Ellipse and set fill color
+Ellipse ellipse = new Ellipse(110, 70);
+ellipse.setFill(Color.LIGHTBLUE);
+```
+
+There are currently 147 predefined colors in the JavaFX Color class, named alphabetically from `ALICEBLUE` to `YELLOWGREEN`. However, you can also specify `Color` using web RGB values with either hexadecimal notation or decimal numbers.
+You can optionally provide an alpha value for transparency. Fully opaque is 1 and fully transparent is 0. A transparency of .5, for example, shows the color but lets the background color show through as well.
+Here are a few examples that set a shape’s fill with `Color`:
+
+```java
+ellipse.setFill(Color.LIGHTBLUE); // Light blue, fully opaque
+ellipse.setFill(Color.web("#ADD8E6")); // Light blue, fully opaque
+ellipse.setFill(Color.web("#ADD8E680")); // Light blue, .5 opaque
+ellipse.setFill(Color.web("0xADD8E6")); // Light blue, fully opaque
+ellipse.setFill(Color.web("0xADD8E680")); // Light blue, .5 opaque
+ellipse.setFill(Color.rgb(173, 216, 230)); // Light blue, fully opaque
+ellipse.setFill(Color.rgb(173, 216, 230, .5)); // Light blue, .5 opaque
+```
+
+Notably, you can interpolate a color’s values, and that is how JavaFX constructs gradients. We’ll show you how to create a linear gradient shortly.
+
+## Text
+
+We next create a Text object. Text is also a `Shape` with additional properties, such as font, text alignment, text, and wrapping width. The constructor provides the text and the `setFont()` method sets its font.
+
+```java
+// Create a Text shape with font and size
+Text text = new Text("My Shapes");
+text.setFont(new Font("Arial Bold", 24));
+```
+
+## The JavaFX Coordinate System
+
+Note that we created the ellipse and text nodes, but they are not yet in our scene graph. Before we add them to the scene, we must put these nodes in some kind of layout container. Layout controls are extremely important in managing your scene graph.
+These controls not only arrange components for you but also respond to events such as resizing, the addition or removal of elements, and any changes to the sizes of one or more nodes in the scene graph.
+
+To show you just how important layout controls are, let’s replace the `StackPane` from the original example with a `Group` and specify the placement manually.
+`Group` is a parent node that manages its children but does not provide any layout capability. Here we create a group and add the ellipse and text elements with the constructor. We then specify group as the scene’s root node:
+
+```java
+Group group = new Group(ellipse, text);
+. . .
+Scene scene = new Scene(group, 350, 230, Color.LIGHTYELLOW);
+```
+
+Group uses default alignment settings for its children and places everything at the origin (0,0), the upper-left corner of the scene. For Text, the default placement is the bottom-left edge of the text element.
+In this case, the only visible portions will be the letters that extend below the bottom edge (the lower case `y` and `p` letters of `MyShapes`). The ellipse will be centered at the group origin (0,0), and therefore only the lower-right quadrant will be visible.
+This arrangement is clearly not what we want. To fix this, let’s manually center the shapes in the 350 × 230 scene, as follows:
+
+```java
+Group group = new Group(ellipse, text);
+// Manually placing components is tedious and error-prone
+ellipse.setCenterX(175);
+ellipse.setCenterY(115);
+text.setX(175-(text.getLayoutBounds().getWidth()/2));
+text.setY(115+(text.getLayoutBounds().getHeight()/2));
+. . .
+Scene scene = new Scene(group, 350, 230, Color.LIGHTYELLOW);
+```
+
+Now the shapes will be nicely centered in the scene. But this is still not ideal. The shapes will remain stuck in the scene at these coordinates when the window resizes (unless you write code that detects and reacts to window resizing).
+And you don’t want to do that. Instead, use JavaFX layout controls!
diff --git a/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/02_layout_controls.md b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/02_layout_controls.md
new file mode 100644
index 0000000..e8ce486
--- /dev/null
+++ b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/02_layout_controls.md
@@ -0,0 +1,160 @@
+---
+id: javafx.fundamentals.layout
+title: JavaFX Layout Controls
+slug: learn/javafx/layout
+type: tutorial-group
+group: rich-client-apps
+category: javafx
+layout: learn/tutorial-group.html
+subheader_select: tutorials
+main_css_id: learn
+toc:
+- Introduction {intro}
+- StackPane {stackpane}
+- AnchorPane {anchorpane}
+- GridPane {gridpane}
+- FlowPane and TilePane {flow-tile-pane}
+- BorderPane {borderpane}
+- SplitPane {splitpane}
+- HBox, VBox, and ButtonBar {box-button}
+- Make a Scene {make-scene}
+description: "Let's take a tour of some common JavaFX layout controls."
+last_update: 2023-09-12
+author: ["GailC.Anderson", "PaulAnderson"]
+byline: 'and is from The Definitive Guide to Modern Java Clients with JavaFX 17 graciously contributed by Apress.'
+---
+
+
+## Introduction
+To manage the nodes of a scene, you use one or more of these controls. Each control is designed for a particular layout configuration. Furthermore, you can nest layout controls to manage groups of nodes and specify how the layout should react to events, such as resizing or changes to the managed nodes.
+You can specify alignment settings as well as margin controls and padding.
+
+There are several ways to add nodes to layout containers. You can add child nodes with the layout container’s constructor. You can also use method `getChildren().add()` for a single node and method `getChildren().addAll()` for multiple nodes. In addition, some layout controls have specialized methods for adding nodes.
+Let’s look at a few commonly used layout controls now to show you how JavaFX can compose a scene for you.
+
+## StackPane
+
+A convenient and easy layout container is `StackPane`. This layout control stacks its children from back to front in the order that you add nodes. Note that we add the ellipse first so that it appears behind the text node. In the opposite order, the ellipse would obscure the text element.
+
+By default, `StackPane` centers all of its children. You can provide a different alignment for the children or apply an alignment to a specific node in the `StackPane`. For example,
+
+```java
+// align the text only
+stackPane.setAlignment(text, Pos.BOTTOM_CENTER);
+```
+
+centers the text node along the bottom edge of the StackPane. Now when you resize the window, the ellipse remains centered, and the text remains anchored to the bottom edge of the window. To specify the alignment of all managed nodes to the bottom edge, use
+
+```java
+// align all managed nodes
+stackPane.setAlignment(Pos.BOTTOM_CENTER);
+```
+
+Although both the ellipse and the text appear at the bottom of the window, they won’t be centered relative to each other. Why not?
+
+## AnchorPane
+
+`AnchorPane` manages its children according to configured anchor points, even when a container resizes. You specify an offset from the pane’s edge for a component. Here, we add a Label to an AnchorPane and anchor it to the lower-left side of the pane with a 10-pixel offset:
+```java
+AnchorPane anchorPane = new AnchorPane();
+Label label = new Label("My Label");
+anchorPane.getChildren().add(label);
+AnchorPane.setLeftAnchor(label, 10.0);
+AnchorPane.setBottomAnchor(label, 10.0);
+```
+
+`AnchorPane` is typically used as a top-level layout manager for controlling margins, even when the window is resized.
+
+## GridPane
+
+`GridPane` lets you place child nodes in a flexibly sized two-dimensional grid. Components can span rows and/or columns, but the row size is consistent for all components in a given row. Similarly, the column’s width is consistent for a given column.
+`GridPane` has specialized methods that add nodes to a particular cell designated by a column and row number. Optional arguments let you specify column and row span values.
+For example, the first label here is placed in the cell corresponding to column 0 and row 0. The second label goes into the cell corresponding to column 1 and row 0, and it spans two columns (the second and third columns). We must also provide a row span value (here it is set to 1):
+
+```java
+GridPane gridPane = new GridPane();
+gridPane.add(new Label("Label1"), 0, 0);
+gridPane.add(new Label("Label2 is very long"), 1, 0, 2, 1);
+```
+
+`GridPane` is useful for laying out components in forms that accommodate columns or rows of various sizes. `GridPane` also allows nodes to span either multiple columns or rows.
+We use `GridPane` in our master-detail UI example (see _Putting It All Together_ section of this series).
+
+## FlowPane and TilePane
+
+`FlowPane` manages its children in either a horizontal or vertical flow. The default orientation is horizontal. You can specify the flow direction with the constructor or use method `setOrientation()`. Here, we specify a vertical orientation with the constructor:
+
+```java
+FlowPane flowpane = new FlowPane(Orientation.VERTICAL);
+```
+`FlowPane` wraps child nodes according to a configurable boundary. If you resize a pane that contains a `FlowPane`, the layout will adjust the flow as needed. The size of the cells depends on the size of the nodes, and it will not be a uniform grid unless all the nodes are the same size. This layout is convenient for nodes whose sizes can vary, such as `ImageView` nodes or shapes. `TilePane` is similar to `FlowPane`, except `TilePane` uses equal-sized cells.
+
+## BorderPane
+
+`BorderPane` is convenient for desktop applications with discreet sections, including a top toolbar (Top), a bottom status bar (Bottom), a center work area (Center), and two side areas (Right and Left).
+Any of the five sections can be empty. Here is an example of a `BorderPane` with a rectangle in the center and a label at the top:
+
+```java
+BorderPane borderPane = new BorderPane();
+Label colorLabel = new Label("Color: Lightblue");
+colorLabel.setFont(new Font("Verdana", 18));
+borderPane.setTop(colorLabel);
+Rectangle rectangle = new Rectangle(100, 50, Color.LIGHTBLUE);
+borderPane.setCenter(rectangle);
+BorderPane.setAlignment(colorLabel, Pos.CENTER);
+BorderPane.setMargin(colorLabel, new Insets(20,10,5,10));
+```
+Note that `BorderPane` uses a center alignment by default for the center area and a left alignment for the top. To keep the top area label centered, we configure its alignment with `Pos.CENTER`. We also set margins around the label with BorderPane static method `setMargin()`. The `Insets` constructor takes four values corresponding to the top, right, bottom, and left edges. Similar alignment and margin configurations apply to other layout components, too.
+
+## SplitPane
+
+`SplitPane` divides the layout space into multiple horizontally or vertically configured areas. The divider is movable, and you typically use other layout controls in each of `SplitPane`’s areas.
+We use `SplitPane` in our master-detail UI example (checkout _Putting It All Together_ part of these series).
+
+## HBox, VBox, and ButtonBar
+
+The `HBox` and `VBox` layout controls provide single horizontal or vertical placements for child nodes. You can nest `HBox` nodes inside a `VBox` for a grid-like effect or nest VBox nodes inside an HBox component.
+`ButtonBar` is convenient for placing a row of buttons of equal size in a horizontal container.
+
+## Make a Scene
+
+Returning to `MyShapes`, the Scene holds the scene graph, defined by its root node.
+
+```java
+package org.modernclient;
+import javafx.application.Application;
+import javafx.scene.Scene;
+import javafx.scene.layout.StackPane;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Ellipse;
+import javafx.scene.text.Font;
+import javafx.scene.text.Text;
+import javafx.stage.Stage;
+public class MyShapes extends Application {
+ @Override
+ public void start(Stage stage) throws Exception {
+ // Create an Ellipse and set fill color
+ Ellipse ellipse = new Ellipse(110, 70);
+ ellipse.setFill(Color.LIGHTBLUE);
+ // Create a Text shape with font and size
+ Text text = new Text("My Shapes");
+ text.setFont(new Font("Arial Bold", 24));
+ StackPane stackPane = new StackPane();
+ stackPane.getChildren().addAll(ellipse, text);
+ Scene scene = new Scene(stackPane, 350, 230,
+ Color.LIGHTYELLOW);
+ stage.setTitle("MyShapes with JavaFX");
+ stage.setScene(scene);
+ stage.show();
+ }
+ public static void main(String[] args) {
+ launch(args);
+ }
+}
+
+```
+First, we construct the `Scene` and provide `stackPane` as the root node. We then specify its width and height in pixels and supply an optional fill argument for the background (`Color.LIGHTYELLOW`).
+What’s left is to configure the Stage. We provide a title, set the scene, and show the stage. The JavaFX runtime renders our scene.
+Below is a hierarchical view of the scene graph for our `MyShapes` application. The root node is the `StackPane`, which contains its two child nodes, `Ellipse` and `Text`.
+
+[](/assets/images/javafx/myshapes-scene-graph.png)
diff --git a/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/03_effects_gradients.md b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/03_effects_gradients.md
new file mode 100644
index 0000000..4eb3e9e
--- /dev/null
+++ b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/03_effects_gradients.md
@@ -0,0 +1,148 @@
+---
+id: javafx.fundamentals.effects
+title: Effects, Gradients and Animations
+slug: learn/javafx/effects
+type: tutorial-group
+group: rich-client-apps
+category: javafx
+layout: learn/tutorial-group.html
+subheader_select: tutorials
+main_css_id: learn
+toc:
+- Enhancing the MyShapes Application {enhance-app}
+- Linear Gradient {linear-gradient}
+- DropShadow {drop-shadow}
+- Reflection {reflection}
+- Configuring Actions {config-actions}
+- Animation {animation}
+description: "Learn how to apply effects, gradients, animations to nodes in your scene graph. "
+last_update: 2023-09-12
+author: ["GailC.Anderson", "PaulAnderson"]
+byline: 'and is from The Definitive Guide to Modern Java Clients with JavaFX 17 graciously contributed by Apress.'
+---
+
+
+## Enhancing the MyShapes Application
+
+One of the advantages of JavaFX over older UI toolkits is the ease in which you can apply effects, gradients, and animation to nodes in your scene graph. We will return to the concept of scene graph nodes repeatedly, since that’s how the JavaFX runtime efficiently renders the visual parts of your application.
+Let’s apply some modifications to `MyShapes` now to show you some of these features. Because JavaFX is able to interpolate colors, you can use colors to define gradients. Gradients give depth to a shape and can be either radial or linear. Let’s show you a linear gradient.
+
+## Linear Gradient
+
+Linear gradients require two or more colors, called Stops. A gradient stop consists of a color and an offset between 0 and 1. This offset specifies where to place the color along the gradient. The gradient calculates the proportional shading from one color stop to the next.
+In our example, we’ll use three color stops: `Color.DODGERBLUE`, `Color.LIGHTBLUE`, and `Color.GREEN`. The first stop will have offset 0, the second offset .5, and the third offset 1.0, as follows:
+
+```java
+Stop[] stops = new Stop[] { new Stop(0, Color.DODGERBLUE),
+ new Stop(0.5, Color.LIGHTBLUE),
+ new Stop(1.0, Color.LIGHTGREEN)};
+```
+
+The `LinearGradient` constructor specifies the x-axis range followed by the y-axis range. The following linear gradient has a constant x-axis but varies its y-axis. This is called a vertical gradient.
+
+```java
+// startX=0, startY=0, endX=0, endY=1
+LinearGradient gradient = new LinearGradient(0, 0, 0, 1, true,CycleMethod.NO_CYCLE, stops);
+```
+
+Boolean true indicates the gradient stretches through the shape (where 0 and 1 are proportional to the shape), and `NO_CYCLE` means the pattern does not repeat. Boolean false indicates the gradient’s x and y values are instead relative to the local coordinate system of the parent.
+To make a horizontal gradient, specify a range for the x-axis and make the y-axis constant, as follows:
+
+```java
+// startX=0, startY=0, endX=1, endY=0
+LinearGradient gradient = new LinearGradient(0, 0, 1, 0, true,CycleMethod.NO_CYCLE, stops);
+```
+Other combinations let you specify diagonal gradients or reverse gradients, where colors appear in the opposite order.
+
+## DropShadow
+Next, let’s add a drop shadow effect to the ellipse. You specify the color of the drop shadow, as well as a radius and x and y offsets. The larger the radius, the larger the shadow.
+The offsets represent the shadow placement relative to the outer edge of the shape. Here, we specify a radius of 30 pixels with an offset of 10 pixels to the right and below the shape:
+
+```java
+ellipse.setEffect(new DropShadow(30, 10, 10, Color.GRAY));
+```
+These offsets simulate a light source emanating from the upper left of the scene. When the offsets are 0, the shadow surrounds the entire shape, as if the light source were shining directly above the scene.
+
+## Reflection
+
+A reflection effect mirrors a component and fades to transparent, depending on how you configure its top and bottom opacities, fraction, and offset. Let’s add a reflection effect to our Text node. We’ll use `.8` for the fraction, so that the reflection will be eight-tenths of the reflected component. The offset specifies how far below the bottom edge the reflection starts in pixels.
+We specify 1 pixel (the default is 0). The reflection starts at fully opaque (top opacity) and transitions to fully transparent (bottom opacity) unless you modify the top and bottom opacity values:
+```java
+Reflection r = new Reflection();
+r.setFraction(.8);
+r.setTopOffset(1.0);
+text.setEffect(r);
+```
+
+You can observe below the enhanced MyShapes program running in a window. You see the linear gradient fill applied to the ellipse, a drop shadow on the ellipse, and the reflection effect applied to the text.
+[](/assets/images/javafx/enahanced-myshapes-application.png)
+
+## Configuring Actions
+
+Now it’s time to make our application do something. JavaFX defines various types of standard input events with the mouse, gestures, touch, or keys. These input event types each have specific handlers that process them.
+
+Let’s keep things simple for now. We’ll show you how to write an event handler to process a single mouse click event. We’ll create the handler and attach it to a node in our scene graph.
+The program’s behavior will vary depending on which node acquires the handler. We can configure the mouse click handler on the text, ellipse, or stack pane node.
+Here’s the code to add an action event handler to the text node:
+
+```java
+text.setOnMouseClicked(mouseEvent -> {
+System.out.println(mouseEvent.getSource().getClass()
++ " clicked.");
+});
+```
+When the user clicks inside the text, the program displays the line `class javafx.scene.text.Text` clicked.
+
+If the user clicks in the background area (the stack pane) or inside the ellipse, nothing happens. If we attach the same listener to the ellipse instead of the text, we see the line
+`class javafx.scene.shape.Ellipse` clicked.
+
+Note that because the text object appears in front of the ellipse in the stack pane, clicking the text object does not invoke the event handler. Even though these scene graph nodes appear on top of each other, they are separate nodes in the hierarchy.
+That is, one isn’t inside the other; rather, they are both distinct leaf nodes managed by the stack pane. In this case, if you want both nodes to respond to a mouse click, you would attach the mouse event handler to both nodes. Or you could attach just one event handler to the stack pane node. Then, a mouse click anywhere inside the window triggers the handler with the following output line:
+`class javafx.scene.layout.StackPane` clicked.
+Let’s do something a bit more exciting and apply an animation to the `MyShape`s program.
+
+## Animation
+JavaFX makes animation very easy when you use the built-in transition APIs. Each JavaFX Transition type controls one or more Node (or Shape) properties. For example, the `FadeTransition` controls a node’s opacity, varying the property over time. To fade something out gradually, you change its opacity from fully opaque (1) to completely transparent (0). The `TranslateTransition` moves a node by modifying its translateX and translateY properties (or translateZ if you’re working in 3D).
+
+You can play multiple transitions in parallel with a `ParallelTransition` or sequentially with a `SequentialTransition`. To control timing between two sequential transitions, use `PauseTransition` or configure a delay before a transition begins with `Transition` method `setDelay()`. You can also define an action when a `Transition` completes using the `Transition` action event handler property `onFinished`.
+
+Transitions begin with method `play()` or `playFromStart()`. Method `play()` starts the transition at its current time; method `playFromStart()` always begins at time 0. Other methods include `stop()` and `pause()`. You can query a transition’s status with `getStatus()`, which returns one of the `Animation.Status` enum values: `RUNNING`, `PAUSED`, or `STOPPED`.
+
+All transitions support the common properties `duration, autoReverse, cycleCount, onFinished, currentTime`, and either node or shape (for Shape-specific transitions).
+
+Let’s define a `RotateTransition` now for our `MyShapes` program. The rotation begins when a user clicks inside the window.
+
+```java
+public class MyShapes extends Application {
+ @Override
+ public void start(Stage stage) throws Exception {
+ . . .
+ // Define RotateTransition
+ RotateTransition rotate = new RotateTransition(
+ Duration.millis(2500), stackPane);
+ rotate.setToAngle(360);
+ rotate.setFromAngle(0);
+ rotate.setInterpolator(Interpolator.LINEAR);
+ // configure mouse click handler
+ stackPane.setOnMouseClicked(mouseEvent -> {
+ if (rotate.getStatus().equals(Animation.Status.RUNNING)) {
+ rotate.pause();
+ } else {
+ rotate.play();
+ }
+ });
+ . . .
+ }
+}
+```
+
+
+The `RotateTransition` constructor specifies a duration of 2500 milliseconds and applies the transition to the `StackPane` node. The rotation animation begins at angle 0 and proceeds linearly to angle 360, providing one full rotation. The animation starts when the user clicks anywhere inside the `StackPane` layout control.
+
+There are a few interesting things to notice in this example. First, because we define the transition on the `StackPane` node, the rotation applies to all of the `StackPane`’s children. This means that not only will the `Ellipse` and `Text` shapes rotate, but the drop shadow and reflection effects rotate, too.
+
+Second, the event handler checks the transition status. If the animation is in progress (running), the event handler pauses the transition. If it’s not running, it starts it up with `play()`. Because `play()` starts at the transition’s current time, a `pause()` followed by `play()` resumes the transition where it was paused.
+
+Below you can see the program running during the rotate transition.
+
+[](/assets/images/javafx/rotate-transition-myshapes-application.png)
diff --git a/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/04_javafx_properties.md b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/04_javafx_properties.md
new file mode 100644
index 0000000..73d9a60
--- /dev/null
+++ b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/04_javafx_properties.md
@@ -0,0 +1,154 @@
+---
+id: javafx.fundamentals.properties
+title: JavaFX Properties
+slug: learn/javafx/properties
+type: tutorial-group
+group: rich-client-apps
+category: javafx
+layout: learn/tutorial-group.html
+subheader_select: tutorials
+main_css_id: learn
+toc:
+- Introduction {intro}
+- Invalidation Listeners {invalidation-listeners}
+- Change Listeners {change-listener}
+- Binding {binding}
+- Bidirectional Binding {unidir-binding}
+- Unidirectional Binding {bidir-binding}
+- Fluent API and Bindings API {fluent-api}
+description: "Control nodes by manipulating their properties. "
+last_update: 2023-09-12
+author: ["GailC.Anderson", "PaulAnderson"]
+byline: 'and is from The Definitive Guide to Modern Java Clients with JavaFX 17 graciously contributed by Apress.'
+---
+
+
+## Introduction
+
+JavaFX property listeners that apply to object properties (not collections) come in two flavors: invalidation listeners and change listeners. Invalidation listeners fire when a property’s value is no longer valid.
+For this example and the ones that follow, we’ll discuss the MyShapesProperties program, which is based on the previous `MyShapes` application. In this new program, we’ve added a second `Text` object placed in a `VBox layout` control below the rotating StackPane.
+Below you can see the updated scene graph with the top-level `VBox`.
+
+[](/assets/images/javafx/myshapes-properties.png)
+
+## Invalidation Listeners
+
+Invalidation listeners have a single method that you override with lambda expressions. Let’s show you the non-lambda expression first, so you can see the full method definition.
+When you click the `StackPane`, the mouse click handler rotates the `StackPane` control as before. The second `Text` object displays the status of the `RotationTransition` animation, which is managed by the read-only status property.
+You’ll see either RUNNING, PAUSED, or STOPPED. The figure below shows the animation paused.
+
+[](/assets/images/javafx/myshapes-properties-invalidation.png)
+
+The invalidation listener includes an observable object that lets you access the property. Because the observable is nongeneric, you must apply an appropriate type cast to access the property value.
+Here’s one way to access the value of the animation’s status property in a listener attached to that property. Note that we attach the listener with the property getter method `statusProperty()`:
+
+```java
+rotate.statusProperty().addListener(new InvalidationListener() {
+ @Override
+ public void invalidated(Observable observable) {
+ text2.setText("Animation status: " +
+ ((ObservableObjectValue)observable).getValue());
+ }
+});
+```
+
+Here we implement the same listener with a lambda expression:
+
+```java
+rotate.statusProperty().addListener(observable -> {
+ text2.setText("Animation status: " +
+ ((ObservableObjectValue)observable).getValue());
+});
+```
+
+Since we access just the status property value, we can bypass the observable with method `getStatus()`, which returns an enum. This avoids the casting expression:
+
+```java
+rotate.statusProperty().addListener(observable -> {
+ text2.setText("Animation status: " + rotate.getStatus());
+});
+```
+
+## Change Listeners
+
+When you need access to the previous value of an observable as well as its current value, use a change listener. Change listeners provide the observable and the new and old values.
+Change listeners can be more expensive, since they must keep track of more information. Here’s the non-lambda version of a change listener that displays both the old and new values.
+Note that you don’t have to cast these parameters, since change listeners are generic:
+
+```java
+rotate.statusProperty().addListener(
+ new ChangeListener() {
+ @Override
+ public void changed(ObservableValue extends Animation.Status> observableValue,
+ Animation.Status oldValue, Animation.Status newValue) {
+ text2.setText("Was " + oldValue + ", Now " + newValue);
+ }
+});
+```
+Here’s the version with a more compact lambda expression:
+
+```java
+rotate.statusProperty().addListener(
+ (observableValue, oldValue, newValue) -> {
+ text2.setText("Was " + oldValue + ", Now " + newValue);
+});
+```
+
+Below you can see the `MyShapesProperties` running with a change listener attached to the animation’s status property. Now we can display both the previous and current values.
+
+[](/assets/images/javafx/myshapes-properties-change.png)
+
+## Binding
+
+JavaFX binding is a flexible, API-rich mechanism that lets you avoid writing listeners in many situations. You use binding to link the value of a JavaFX property to one or more other JavaFX properties.
+Property bindings can be unidirectional or bidirectional. When properties are the same type, the unidirectional `bind()` method may be all you need.
+However, when properties have different types or you want to compute a value based on more than one property, then you’ll need the fluent and bindings APIs.
+You can also create your own binding methods with custom binding.
+
+## Unidirectional Binding
+
+The simplest form of binding links the value of one property to the value of another. Here, we bind text2’s rotate property to `stackPane`’s rotate property:
+
+```java
+text2.rotateProperty().bind(stackPane.rotateProperty());
+```
+
+This means any changes to `stackPane`’s rotation will immediately update text2’s rotate property. When this binding is set in the `MyShapesProperties` program, any clicks inside the `StackPane` initiate a rotate transition.
+This makes both the `StackPane` and text2 components rotate together. The `StackPane` rotates because we start the `RotateTransition` defined for that node. The text2 node rotates because of the bind expression.
+
+Note that when you bind a property, you cannot explicitly set its value unless you unbind the property first.
+
+## Bidirectional Binding
+
+Bidirectional binding provides a two-way relationship between two properties. When one property updates, the other also updates. Here’s an example with two text properties:
+
+```java
+text2.textProperty().bindBidirectional(text.textProperty());
+```
+Both text controls initially display "My Shapes". When the user clicks inside the `stackPane` and the `stackPane` rotates, both text properties will now contain the animation status because of the change listener.
+
+Bidirectional binding is not completely symmetrical; the initial value of both properties takes on the value of the property passed in the call to `bindBidirectional()`. Unlike `bind()`, you can explicitly set either property when using bidirectional binding.
+
+## Fluent API and Bindings API
+
+The fluent and bindings APIs help you construct bind expressions when more than one property needs to participate in a binding or when it’s necessary to perform some sort of calculation or conversion.
+For example, the following bind expression displays the rotation angle of the `StackPane` as it rotates from 0 to 360 degrees. The text property is a `String`, and the rotate property is a double.
+The binding method `asString()` converts the double to `String`, formatting the number with a single digit to the right of the decimal point:
+
+```java
+text2.textProperty().bind(stackPane.rotateProperty().asString("%.1f"));
+```
+
+For a more complex example, let’s update `text2`’s stroke property (its color) depending on whether the animation is running or not. Here we construct a binding with `When` based on a ternary expression.
+This sets the stroke color to green when the animation is running and to red when the animation is stopped or paused:
+
+```java
+text2.strokeProperty().bind(new When(rotate.statusProperty()
+ .isEqualTo(Animation.Status.RUNNING))
+ .then(Color.GREEN).otherwise(Color.RED));
+```
+
+The `text2` text property is set in the change listener that is attached to the animation status property we showed earlier.
+You can see below the application `MyShapesProperties` with the complex bind expression attached to the `text2 strokeProperty`. Since the animation is running, the stroke property is set to `Color.GREEN`.
+
+[](/assets/images/javafx/myshapes-properties-fluent.png)
diff --git a/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/05_fxml.md b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/05_fxml.md
new file mode 100644
index 0000000..257a77e
--- /dev/null
+++ b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/05_fxml.md
@@ -0,0 +1,247 @@
+---
+id: javafx.fundamentals.fxml
+title: Using FXML
+slug: learn/javafx/fxml
+type: tutorial-group
+group: rich-client-apps
+category: javafx
+layout: learn/tutorial-group.html
+subheader_select: tutorials
+main_css_id: learn
+toc:
+- Declare Scene Graph Nodes with FXML {intro}
+- Controller Class {controller-class}
+- JavaFX Application Class {javafx-app-class}
+- Adding CSS {add-css}
+- Using Scene Builder {scene-builder}
+description: "Control nodes by manipulating their properties. "
+last_update: 2023-09-12
+author: ["GailC.Anderson", "PaulAnderson"]
+byline: 'and is from The Definitive Guide to Modern Java Clients with JavaFX 17 graciously contributed by Apress.'
+---
+
+
+## Declare Scene Graph Nodes with FXML
+
+You’ve seen how JavaFX APIs create scene graph nodes and configure them for you. The `MyShapes` and `MyShapesProperties` programs use only JavaFX code to build and configure these objects. An alternative approach is to declare scene graph nodes with FXML, a markup notation based on XML. FXML lets you describe and configure your scene graph in a declarative format. This approach has several advantages:
+* FXML markup structure is hierarchical, so it reflects the structure of your scene graph.
+* FXML describes your view and supports a Model-View-Controller (MVC) architecture, providing better structure for larger applications.
+* FXML reduces the JavaFX code you have to write to create and configure scene graph nodes.
+* You can design your UI with Scene Builder. This drag-and-drop tool is a stand-alone application that provides a visual rendering of your scene. And Scene Builder generates the FXML markup for you.
+* You can also edit your FXML markup with text and IDE editors.
+
+FXML affects the structure of your program. The main application class now invokes an `FXMLLoader`. This loader parses your FXML markup, creates JavaFX objects, and inserts the scene graph into the scene at the root node. You can have multiple FXML files, and typically each one has a corresponding JavaFX controller class. This controller class may include event handlers or other statements that dynamically update the scene.
+The controller also includes business logic that manages a specific view.
+
+Let’s return to our `MyShapes` example (now called `MyShapesFXML`) and use an FXML file for the view and CSS for styling. Below you can see the files in our program, arranged for use with build tools or IDEs.
+
+[](/assets/images/javafx/myshapes-fxml-css.png)
+
+The JavaFX source code appears under the java subdirectory. The resources subdirectory contains the FXML and CSS files (here `Scene.fxml` and `Styles.css`).
+
+This program includes a rotating `StackPane`, `VBox` control, and second `Text` object. `Scene.fxml` describes our scene graph: a top-level `VBox` that includes a `StackPane` and `Text` element. The `StackPane` includes the `Ellipse` and `Text` shapes.
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+The top-level container includes the name of the JavaFX controller class with attribute fx:controller. The `VBox` specifies its alignment, preferred sizes, and spacing followed by its children: the `StackPane` and `Tex`t.
+Here, we configure the `StackPane` with preferred sizing. A special attribute `fx:id` specifies a variable name corresponding to this node.
+In the JavaFX controller class, you’ll now see this variable name annotated with `@FXML` for the `StackPane`. This is how you access objects in the controller class that are declared in FXML files.
+
+In addition, `StackPane` specifies an `onMouseClicked` event handler called `#handleMouseClick`. This event handler is also annotated with `@FXML` in the JavaFX controller class.
+
+Here, the `StackPane` children, `Ellipse` and `Text`, are declared inside the Children FXML node. Neither has associated `fx:id` attributes, since the controller class does not need to access these objects. You also see the linear gradient, drop shadow, and reflection effect configurations.
+
+Note that the `Text` object with `fx:id text2` appears after the `StackPane` definition. This makes the second `Text` object appear under the `StackPane` in the `VBox`. We also specify an `fx:id` attribute to access this node from the JavaFX controller.
+
+## Controller Class
+
+Let’s show you the controller class now. You’ll notice the code is more compact, since object instantiations and configuration code are no longer done with Java statements. All that is now specified in the FXML markup.
+
+```java
+package org.modernclient;
+import javafx.animation.Animation;
+import javafx.animation.Interpolator;
+import javafx.animation.RotateTransition;
+import javafx.beans.binding.When;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.layout.StackPane;
+import javafx.scene.paint.Color;
+import javafx.scene.text.Text;
+import javafx.util.Duration;
+import java.net.URL;
+import java.util.ResourceBundle;
+public class FXMLController implements Initializable {
+ @FXML
+ private StackPane stackPane;
+ @FXML
+ private Text text2;
+ private RotateTransition rotate;
+ @Override
+ public void initialize(URL url, ResourceBundle rb) {
+ rotate = new RotateTransition(Duration.millis(2500), stackPane);
+ rotate.setToAngle(360);
+ rotate.setFromAngle(0);
+ rotate.setInterpolator(Interpolator.LINEAR);
+ rotate.statusProperty().addListener(
+ (observableValue, oldValue, newValue) -> {
+ text2.setText("Was " + oldValue + ", Now " + newValue);
+ });
+ text2.strokeProperty().bind(new When(rotate.statusProperty()
+ .isEqualTo(Animation.Status.RUNNING))
+ .then(Color.GREEN).otherwise(Color.RED));
+ }
+ @FXML
+ private void handleMouseClick(MouseEvent mouseEvent) {
+ if (rotate.getStatus().equals(Animation.Status.RUNNING)) {
+ rotate.pause();
+ } else {
+ rotate.play();
+ }
+ }
+}
+```
+
+The controller class implements `Initializable` and overrides method `initialize()`, which is invoked for you at runtime. Importantly, the private class fields `stackPane` and `text2` are annotated with `@FXML`.
+The `@FXML` annotation associates variable names in the controller class to the objects described in the FXML file. There is no code in the controller class that creates these objects because the `FXMLLoader` does that for you.
+
+The `initialize()` method does three things here. First, it creates and configures the `RotateTransition` and applies it to the `stackPane` node. Second, it adds a change listener to the transition’s status property.
+And third, a bind expression for the `text2` stroke property specifies its color based on the rotate transition’s status.
+
+The `@FXML` annotation with `handleMouseClick()` indicates that the FXML file configures the event handler. This mouse click event handler starts and stops the rotate transition’s animation.
+
+## JavaFX Application Class
+
+The main application class, `MyShapesFXML`, now becomes very simple. Its job is to invoke the `FXMLLoader`, which parses the FXML (`Scene.fxml`), builds the scene graph, and returns the scene graph root. All you have to do is build the scene object and configure the stage as before, as shown below.
+
+```java
+package org.modernclient;
+import javafx.application.Application;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.scene.paint.Color;
+import javafx.stage.Stage;
+public class MyShapesFXML extends Application {
+ @Override
+ public void start(Stage stage) throws Exception {
+ Parent root = FXMLLoader.load(getClass()
+ .getResource("/fxml/Scene.fxml"));
+ Scene scene = new Scene(root, Color.LIGHTYELLOW);
+ scene.getStylesheets().add(getClass()
+ .getResource("/styles/Styles.css").toExternalForm());
+ stage.setTitle("MyShapesApp with JavaFX");
+ stage.setScene(scene);
+ stage.show();
+ }
+ public static void main(String[] args) {
+ launch(args);
+ }
+}
+```
+
+## Adding CSS
+
+Now let’s show you how to incorporate your own styles with CSS. One advantage of JavaFX is its ability to style nodes with CSS.
+JavaFX comes bundled with a default stylesheet, `Modena.css`. You can augment these default styles or replace them with new ones. Our example CSS file found in file Styles.css is a single style class (`mytext`) that sets its font style to italic:
+
+```css
+.mytext {
+ -fx-font-style: italic;
+}
+```
+
+To use this stylesheet, you must first load the file, either in the application’s `start()` method or in the FXML file. Once the file is added to the available stylesheets, you can apply the style classes to a node. To apply individually defined style classes to a specific node, for instance, use:
+
+```java
+text2.getStyleClass().add("mytext");
+```
+
+Here, `mytext` is the style class and `text2` is the second `Text` object in our program.
+Alternatively, you can specify the stylesheet in the FXML file. The advantage of this approach is that styles are now available inside Scene Builder. Here is the modified `Scene.fxml` file that loads this customized CSS file and applies the customized CSS style class to the `text2 Text` node:
+
+```xml
+. . .
+
+
+
+
+ . . . code removed . . .
+
+
+
+
+
+
+
+
+```
+
+## Using Scene Builder
+
+Scene Builder was originally developed at Oracle and is now open sourced. It is available for download from Gluon here: https://gluonhq.com/products/scene-builder/.
+Scene Builder is a stand-alone drag-and-drop tool for creating JavaFX UIs. You can see below the main Scene Builder window with file `Scene.fxml` from the `MyShapesFXML` program.
+
+[](/assets/images/javafx/scene-builder.png)
+
+The upper-left window shows the JavaFX component library. This library includes containers, controls, shapes, 3D, and more. From this window, you select components and drop them onto your scene in the middle visual view or onto the `Document` window shown in the lower-left area.
+
+The `Document` window shows the scene graph hierarchy. You can select components and move them within the tree. The right window is an `Inspector` window that lets you configure each component, including its properties, layout settings, and code. In this figure, the `StackPane` is selected in the `Document` hierarchy window and appears in the center visual view.
+In the `Inspector` window, the `OnMouseClicked` property is set to `#handleMouseClick`, which is the name of the corresponding method in the JavaFX controller class.
+
+Scene Builder is particularly helpful when building real-world form-based UIs. You can visualize your scene hierarchy and easily configure layout and alignment settings.
+
diff --git a/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/06_all_together.md b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/06_all_together.md
new file mode 100644
index 0000000..af66631
--- /dev/null
+++ b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/06_all_together.md
@@ -0,0 +1,452 @@
+---
+id: javafx.fundamentals.all
+title: Putting all together
+slug: learn/javafx/all
+type: tutorial-group
+group: rich-client-apps
+category: javafx
+layout: learn/tutorial-group.html
+subheader_select: tutorials
+main_css_id: learn
+toc:
+- Overview {overview}
+- Master-Detail UI {master-detail-ui}
+- The Model {model}
+- Observable Lists {observable-lists}
+- Implementing ListView Selection {list-view-selection}
+- Using Multiple Selection {multiple-selection}
+- ListView and Sort {list-view-sort}
+- Person UI Application Actions {app-actions}
+- Person UI with Records {app-records}
+- Key Point Summary {summary}
+description: "Control nodes by manipulating their properties. "
+last_update: 2023-09-12
+author: ["GailC.Anderson", "PaulAnderson"]
+byline: 'and is from The Definitive Guide to Modern Java Clients with JavaFX 17 graciously contributed by Apress.'
+---
+
+
+## Overview
+
+It’s time to build a more interesting JavaFX application now, one that implements a master-detail view. As we show you this application, we’ll explain several JavaFX features that help you control the UI and keep your data and the application consistent.
+
+First, we use Scene Builder to construct and configure the UI. Our example includes a `Person` model class and an underlying `ObservableList` that holds data. The program lets users make changes, but we don’t persist any data.
+JavaFX has `ObservableLists` that manage collections of data, and you can write listeners and bind expressions that respond to any data changes. The program uses a combination of event handlers and bind expressions to keep the application state consistent.
+
+## Master-Detail UI
+
+For the UI, we use a JavaFX ListView control in the left window (the master view) and a `Form` on the right (the detail view). In Scene Builder, we select an `AnchorPane` as the top-level component and the scene graph root.
+A `SplitPane` layout pane divides the application view into two parts, and each part has `AnchorPane` as its main container.
+
+[](/assets/images/javafx/person-ui-app.png)
+
+The `ListView` control lets you perform selections for a `Person` object. Here, the first `Person` is selected, and the details of that `Person` appear in the form control on the right.
+The form control has the following layout:
+* The form contains a `GridPane` (two columns by four rows) that holds `TextFields` for the firstname and lastname fields of `Person`.
+* A `TextArea` holds the notes field for `Person`. Labels in the first column mark each of these controls.
+* The bottom row of the `GridPane` consists of a `ButtonBar` that spans both columns and aligns on the right side by default. The `ButtonBar` sizes all of its buttons to the width of the widest button label so the buttons have a uniform size.
+* The buttons let you perform New (create a `Person` and add that `Person` to the list), Update (edit a selected `Person`), and Delete (remove a selected `Person` from the list).
+* Bind expressions query the state of the application and enable or disable the buttons.
+
+The hierarchical view of our scene graph for the Person UI application looks like following:
+
+[](/assets/images/javafx/person-ui-scene-graph.png)
+
+The file structure of the application is listed below:
+
+[](/assets/images/javafx/person-ui-file-struct.png)
+
+`Person.java` contains the `Person` model code, and `SampleData.java` provides the data to initialize the application. `FXMLController.java` is the JavaFX controller class, and `PersonUI.java` holds the main application class. Under resources, the FXML file `Scene.fxml` describes the UI.
+
+## The Model
+
+The `Person` class is the "model: we use for this application.
+
+```java
+package org.modernclient.model;
+import javafx.beans.Observable;
+import javafx.beans.property.SimpleStringProperty;
+import javafx.beans.property.StringProperty;
+import javafx.util.Callback;
+import java.util.Objects;
+public class Person {
+ private final StringProperty firstname = new SimpleStringProperty(
+ this, "firstname", "");
+ private final StringProperty lastname = new SimpleStringProperty(
+ this, "lastname", "");
+ private final StringProperty notes = new SimpleStringProperty(
+ this, "notes", "sample notes");
+ public Person() {
+ }
+ public Person(String firstname, String lastname, String notes) {
+ this.firstname.set(firstname);
+ this.lastname.set(lastname);
+ this.notes.set(notes);
+ }
+ public String getFirstname() {
+ return firstname.get();
+ }
+ public StringProperty firstnameProperty() {
+ return firstname;
+ }
+ public void setFirstname(String firstname) {
+ this.firstname.set(firstname);
+ }
+ public String getLastname() {
+ return lastname.get();
+ }
+ public StringProperty lastnameProperty() {
+ return lastname;
+ }
+ public void setLastname(String lastname) {
+ this.lastname.set(lastname);
+ }
+ public String getNotes() {
+ return notes.get();
+ }
+ public StringProperty notesProperty() {
+ return notes;
+ }
+ public void setNotes(String notes) {
+ this.notes.set(notes);
+ }
+ @Override
+ public String toString() {
+ return firstname.get() + " " + lastname.get();
+ }
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null || getClass() != obj.getClass()) return false;
+ Person person = (Person) obj;
+ return Objects.equals(firstname, person.firstname) &&
+ Objects.equals(lastname, person.lastname) &&
+ Objects.equals(notes, person.notes);
+ }
+ @Override
+ public int hashCode() {
+ return Objects.hash(firstname, lastname, notes);
+ }
+}
+```
+
+## Observable Lists
+
+When working with JavaFX collections, you’ll typically use `ObservableLists` that detect list changes with listeners. Furthermore, the JavaFX controls that display lists of data expect observable lists.
+These controls automatically update the UI in response to list modifications. We’ll explain some of these intricacies as we walk you through our example program.
+
+## Implementing ListView Selection
+
+A `ListView` control displays items in an observable list and lets you select one or possibly multiple items. To display a selected `Person` in the form fields in the right view, you use a change listener for the `selectedItemProperty`.
+This change listener is invoked each time the user either selects a different item from the `ListView` or deselects the selected item. You can use the mouse for selecting, as well as the arrow keys, Home (for the first item), and End (for the last item).
+On a Mac, use Fn + Left Arrow for Home and Fn + Right Arrow for End. For deselecting (either Command-click for a Mac or Control-click on Linux or Windows), the new value is null, and we clear all the form control fields.
+Below you can observe the `ListView` selection change listener.
+
+```java
+listView.getSelectionModel().selectedItemProperty().addListener(
+ personChangeListener = (observable, oldValue, newValue) -> {
+ // newValue can be null if nothing is selected
+ selectedPerson = newValue;
+ modifiedProperty.set(false);
+ if (newValue != null) {
+ // Populate controls with selected Person
+ firstnameTextField.setText(selectedPerson.getFirstname());
+ lastnameTextField.setText(selectedPerson.getLastname());
+ notesTextArea.setText(selectedPerson.getNotes());
+ } else {
+ firstnameTextField.setText("");
+ lastnameTextField.setText("");
+ notesTextArea.setText("");
+ }
+ });
+```
+
+Boolean property `modifiedProperty` tracks whether the user has changed any of the three text controls in the form. We reset this flag after each `ListView` selection and use this property in a bind expression to control the Update button’s disable property.
+
+## Using Multiple Selection
+
+By default, a `ListView` control implements single selection so at most one item can be selected. `ListView` also provides multiple selection, which you enable by configuring the selection mode, as follows:
+
+```java
+listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
+```
+
+With this setting, each time the user adds another item to the selection with CTRL-Shift or CTRL-Command, the `selectedItemProperty` listener is invoked with the new selection.
+The `getSelectedItems()` method returns all of the currently selected items, and the newValue argument is the most recently selected value. For example, the following change listener collects multiple selected items and prints them:
+
+```java
+listView.getSelectionModel().selectedItemProperty().addListener(
+ personChangeListener = (observable, oldValue, newValue) -> {
+ ObservableList selectedItems = listView.getSelectionModel().getSelectedItems();
+ // Do something with selectedItems
+ // System.out.println(selectedItems);
+});
+```
+Our Person UI application uses single selection mode for the ListView.
+
+## ListView and Sort
+
+Suppose you want to sort the list of names by last name and then first name. JavaFX has several ways to sort lists. Since we need to keep names sorted, we’ll wrap the underlying `ObservableArrayList` in a `SortedList`.
+To keep the list sorted in ListView, we invoke `ListView`’s setItems() method with the sorted list. A comparator specifies the ordering.
+First, we compare each person’s last name for sorting and then the first names if necessary. To set the sorting, the `setComparator()` method uses an anonymous class or, more succinctly, a lambda expression:
+
+```java
+// Use a sorted list; sort by lastname; then by firstname
+SortedList sortedList = new SortedList(personList);
+sortedList.setComparator((p1, p2) -> {
+ int result = p1.getLastname().compareToIgnoreCase(p2.getLastname());
+ if (result == 0) {
+ result = p1.getFirstname().compareToIgnoreCase(p2.getFirstname());
+ }
+ return result;
+});
+listView.setItems(sortedList);
+```
+
+Note that the comparator arguments p1 and p2 are inferred as `Person` types since `SortedList` is generic.
+
+## Person UI Application Actions
+
+Our Person UI application implements three actions: Delete (remove the selected `Person` object from the underlying list), New (create a `Person` object and add it to the underlying list), and Update (make changes to the selected `Person` object and update the underlying list).
+Let’s go over each action in detail, with an eye toward learning more about the JavaFX features that help you build this type of application.
+
+### Delete a Person
+
+The controller class includes an action event handler for the Delete button. Here’s the FXML snippet that defines the Delete button:
+
+```xml
+
+```
+
+The `fx:id` attribute names the button so the JavaFX controller class can access it. The `onAction` attribute corresponds to the `ActionEvent` handler in the controller code. We’re not using keyboard shortcuts in this application, so we set attribute `mnemonicParsing` to false.
+
+---
+**NOTE**
+
+When mnemonic parsing is true, you can specify a keyboard shortcut to activate a labeled control, such as Alt-F to open a File menu, for example. You define the keyboard shortcut by preceding the targeted letter with an underbar character in the label.
+
+---
+
+You cannot update a `SortedList` directly, but you can apply changes to its underlying list (`ObservableList personList`). The `SortedList` always keeps its elements sorted whenever you add or delete items.
+
+Here is the event handler in the controller class:
+
+```java
+@FXML
+private void removeButtonAction(ActionEvent actionEvent) {
+ personList.remove(selectedPerson);
+}
+```
+
+This handler removes the selected `Person` object from the backing observable array list. The `ListView` control’s selection change listener sets `selectedPerson`.
+
+Note that we don’t have to check `selectedPerson` against null here. Why not? You’ll see that we disable the Delete button when the `selectedItemProperty` is null.
+This means the Delete button’s action event handler can never be invoked when the user deselects an element in the ListView control. Here’s the bind expression that controls the Delete button’s disable property:
+
+```java
+removeButton.disableProperty().bind(
+listView.getSelectionModel().selectedItemProperty().isNull());
+```
+
+This elegant statement makes the event handler more compact and subsequently less error prone. Both the button `disableProperty` and the selection model `selectedItemProperty` are JavaFX observables.
+You can therefore use them in bind expressions. The property that invokes `bind()` automatically updates when the `bind()` arguments’ values change.
+
+### Add a Person
+
+The New button adds a `Person` to the list and subsequently updates the `ListView` control. A new item is always sorted because the list re-sorts when elements are added to the wrapped list.
+Here is the FXML that defines the New button. Similar to the Delete button, we define both the `fx:id` and `onAction` attributes:
+
+```java
+
+```
+
+Under what circumstances should we disable the New button?
+* When clicking New, no items in the `ListView` should be selected. Therefore, we disable the New button if the `selectedItemProperty` is not null. Note that you can deselect the selected item with Command-click or Control-click.
+* We should not create a new `Person` if either the first or last name field is empty. So we disable the New button if either of these fields is empty. We do allow the Notes field to be empty, however.
+Here is the bind expression that implements these restrictions:
+
+```java
+createButton.disableProperty().bind(
+ listView.getSelectionModel().selectedItemProperty().isNotNull()
+ .or(firstnameTextField.textProperty().isEmpty()
+ .or(lastnameTextField.textProperty().isEmpty())));
+
+```
+
+Now let’s show you the New button event handler:
+
+```java
+@FXML
+private void createButtonAction(ActionEvent actionEvent) {
+ Person person = new Person(firstnameTextField.getText(),
+ lastnameTextField.getText(), notesTextArea.getText());
+ personList.add(person);
+ // and select it
+ listView.getSelectionModel().select(person);
+}
+```
+First, we create a new `Person` object using the form’s text controls and add this `Person` to the wrapped list (`ObservableList personList`). To make the `Person`’s data visible and editable right away, we select the newly added `Person`.
+
+### Update a Person
+
+An update of a `Person` is not as straightforward as the other operations. Before we delve into the details of why, let’s first look at the Update button’s FXML code, which is similar to the other buttons:
+
+```xml
+
+```
+
+By default, a sorted list does not respond to individual array elements that change. For example, if `Person` "Ethan Nieto" changes to "Ethan Abraham", the list will not re-sort the way it does when items are added or removed.
+There’s two ways to fix this. First is to remove the item and add it back again with the new values.
+
+The second way is to define an extractor for the underlying object. An extractor defines properties that should be observed when changes occur. Normally, changes to individual list elements are not observed. Observable objects returned by the extractor flag update changes in a list ChangeListener.
+Thus, to make a `ListView` control display a properly sorted list after changes to individual elements, you need to define an `ObservableList` with an extractor.
+
+The benefit of extractors is that you only include the properties that affect sorting. In our example, properties firstname and lastname affect the list’s order. These properties should go in the extractor.
+
+An extractor is a static callback method in the model class. Here’s the extractor for our `Person` class:
+
+```java
+public class Person {
+ ...
+ public static Callback extractor =
+ p-> new Observable[] {p.lastnameProperty(), p.firstnameProperty()};
+}
+```
+
+Now the controller class can use this extractor to declare an `ObservableList` called `personList`, as follows:
+
+```java
+private final ObservableList personList =
+ FXCollections.observableArrayList(Person.extractor);
+```
+
+With the extractor set up, the sorted list detects changes in both `firstnameProperty` and `lastnameProperty` and re-sorts as needed.
+
+Next, we define when the Update button is enabled. In our application, the Update button should be disabled if no items are selected or if either the `firstname` or `lastname` text field becomes empty.
+And finally, we disable Update if the user has not yet made changes to the form’s text components. We track these changes with a JavaFX Boolean property called modifiedProperty, created with the JavaFX Boolean property helper class, `SimpleBooleanProperty`.
+We initialize this Boolean to false in the JavaFX controller class, as follows:
+
+```java
+private final BooleanProperty modifiedProperty = new SimpleBooleanProperty(false);
+```
+
+We reset this Boolean property to false in the `ListView` selection change listener. The `modifiedProperty` is set to true when a keystroke occurs in any of the three fields that can change: the first name, last name, and notes controls.
+Here is the keystroke event handler, which is invoked when a key stroke is detected inside the focus for each of these three controls:
+
+```java
+@FXML
+private void handleKeyAction(KeyEvent keyEvent) {
+ modifiedProperty.set(true);
+}
+```
+
+Of course, the FXML markup must configure attribute `onKeyReleased` for all three text controls to invoke the keystroke event handler. Here is the FXML for the firstname `TextField`, which links the `handleKeyAction` event handler to a key release event for this control:
+
+```xml
+
+```
+
+And here is the bind expression for the Update button, which is disabled if the `selectedItemProperty` is null, the `modifiedProperty` is false, or the text controls are empty:
+
+```java
+updateButton.disableProperty().bind(
+ listView.getSelectionModel().selectedItemProperty().isNull()
+ .or(modifiedProperty.not())
+ .or(firstnameTextField.textProperty().isEmpty()
+ .or(lastnameTextField.textProperty().isEmpty())));
+```
+
+Now let’s show you the Update button’s action event handler. This handler is invoked when the user clicks the Update button after selecting an item in the `ListView` control and making at least one change to any of the text fields.
+
+
+But there is one more housekeeping chore to do. Before starting the update of the selected item with the values from the form controls, we must remove the listener on the `selectedItemProperty`.
+Why? Recall that changes to the `firstname` or `lastname` properties will affect the list dynamically and possibly re-sort it. Furthermore, this may change `ListView`’s idea of the currently selected item and invoke the `ChangeListener`.
+To prevent this, we remove the listener during the update and add the listener back when the update finishes. During the update, the selected item remains unchanged (even if the list re-sorts).
+Thus, we clear the `modifiedProperty` flag to ensure the Update button gets disabled:
+
+```java
+@FXML
+private void updateButtonAction(ActionEvent actionEvent) {
+ Person p = listView.getSelectionModel().getSelectedItem();
+ listView.getSelectionModel().selectedItemProperty()
+ .removeListener(personChangeListener);
+ p.setFirstname(firstnameTextField.getText());
+ p.setLastname(lastnameTextField.getText());
+ p.setNotes(notesTextArea.getText());
+ listView.getSelectionModel().selectedItemProperty()
+ .addListener(personChangeListener);
+ modifiedProperty.set(false);
+}
+```
+
+## Person UI with Records
+
+One of the exciting new features in Java 16 is records. Records allow you to model classes that hold immutable data and describe state, often with a single line of code.
+Let’s refactor our Person UI example to use Java records for the `Person` model class. We do this for several reasons.
+* Modern Java clients with JavaFX will continue to evolve as applications leverage new Java features. Afterall, JavaFX is implemented with Java APIs and can certainly take advantage of new features as they become available.
+* Our UI example is a good candidate for records, since using a Person record instead of a class is a straightforward approach.
+* We originally implemented `Person` with JavaFX properties, which are observable and mutable. But, in the context of our application, is this mutability necessary or even desirable?
+• Java records help make your code more readable, since often a single line defines the state of your model class.
+
+### Person Record
+
+We declare a record with its name and its immutable components; each component has a name and type. These components are final instance fields in the generated class.
+Java generates accessor methods for the fields, a constructor, and default implementations for methods `equals()`, `hashCode()`, and `toString()`.
+
+Here’s the new `Person` class, which is much shorter than the non-record version:
+
+```java
+public record Person (String firstname, String lastname, String notes) {
+ @Override
+ public String toString() {
+ return firstname + " " + lastname;
+ }
+}
+```
+
+Note that we supply our own `toString()` implementation to replace the auto-generated `toString()`, since `ListView` uses this to display each `Person` object.
+The generated accessor methods are `firstname()`, `lastname()`, and `notes()` to match the elements declared in the record header. We update our application to use these names instead of the conventional getter forms.
+This affects the `selectedItemProperty` change listener and the sorted list comparator.
+
+No changes are necessary to the `createButtonAction` or `removeButtonAction` event handlers. There is also no change to the code that creates our sample list of `Person` objects (`SampleData.java`).
+
+Records do require changes to the `updateButtonAction` event handler, however. Since a `Person` object is now immutable, we cannot update its fields. Therefore, to update a `Person`, we must create a new `Person` object, remove the old one, and add the new one to the backing list.
+The sorted list automatically updates with the new data. Here is the new `updateButtonAction` event handler.
+
+```java
+@FXML
+private void updateButtonAction(ActionEvent actionEvent) {
+ Person person = new Person(firstnameTextField.getText(), lastnameTextField.getText(),
+ notesTextArea.getText());
+ personList.remove(listView.getSelectionModel().getSelectedItem());
+ personList.add(person);
+ listView.getSelectionModel().select(person);
+ modifiedProperty.set(false);
+}
+```
+
+By removing and adding a `Person`, the update process becomes simpler. The extractor to detect changes is no longer necessary, nor do we need to temporarily remove the `selectedItemProperty` change listener during updates.
+
+By restricting `Person` to be an immutable container, we greatly simplify `Person` and the readability of our program. However, JavaFX properties and binding are still ideal features to maintain the state of the UI.
+
+## Key Point Summary
+
+This series has covered a lot of ground. Let’s review the key points:
+* JavaFX is a modern UI toolkit that runs efficiently in desktop, mobile, and embedded environments.
+* JavaFX uses a theater metaphor. The runtime system creates the primary stage and invokes the `start()` method of your application.
+* You create a hierarchical scene graph and install the root node in the scene.
+* The JavaFX runtime system performs all UI updates and scene graph modifications on the JavaFX Application Thread. Any long-running work should be relegated to background tasks in separate threads to keep the UI responsive. JavaFX has a well-developed concurrency library that helps you keep UI code separate from background code.
+* JavaFX supports both 2D and 3D graphics. The origin in 2D graphics is the upper-left corner of the scene.
+* JavaFX includes a rich set of layout controls that let you arrange components in a scene. You can nest layout controls and specify resizing criteria.
+* JavaFX defines a scene graph as a hierarchical collection of Nodes. Nodes are described by their properties.
+* JavaFX properties are observable. You can attach listeners and use the rich bindings APIs to link properties to each other and detect changes.
+* JavaFX lets you define high-level animations called transitions.
+* The hierarchical nature of the scene graph means parent nodes can delegate rendering work to their children.
+* JavaFX supports a wide range of events that let you react to user inputs and changes to a scene graph.
+* While you can write JavaFX applications completely in Java, a better approach is to write visual descriptions in FXML, a markup language for specifying UI content. FXML helps separate visual code from model and controller code.
+* Each FXML file typically describes a scene and configures a controller.
diff --git a/app/pages/learn/01_tutorial/07_more-resources/01_java-certification-overview.md b/app/pages/learn/01_tutorial/08_more-resources/01_java-certification-overview.md
similarity index 100%
rename from app/pages/learn/01_tutorial/07_more-resources/01_java-certification-overview.md
rename to app/pages/learn/01_tutorial/08_more-resources/01_java-certification-overview.md
diff --git a/app/pages/learn/01_tutorial/08_references/01_references.md b/app/pages/learn/01_tutorial/09_references/01_references.md
similarity index 100%
rename from app/pages/learn/01_tutorial/08_references/01_references.md
rename to app/pages/learn/01_tutorial/09_references/01_references.md
diff --git a/app/templates/pages/learn/index.html b/app/templates/pages/learn/index.html
index 80b3cb9..682ee4a 100644
--- a/app/templates/pages/learn/index.html
+++ b/app/templates/pages/learn/index.html
@@ -89,6 +89,18 @@
Getting to know the JVM
+
+
Rich Client Applications
+
+ {% for tutorial in file.data.tutorials.javafx | sort(false, false, "category_order") %}
+