Scrollable Twitter Bootstrap Menus

Twitter Bootstrap has some pretty nice features, but one of the more common practices left out of its component stack is the ability to have long dropdown menus that scroll their content. A quick search on Google brought up some possible solutions, but a lot of them involved JavaScript/Plug-ins or directly hacked into the CSS of bootstrap. Each of which have their own advantages (JavaScript allows for more control, CSS only prevents unnecessary DOM manipulations). However, I’ve typically find myself consumed in the abyss of hacks that worked, but just so happened to brake another functionality of the framework I’m using. Sure, the hack may work in a lot of situations… but just not mine :( So, instead of taking another performance hit with a JS solution or using just CSS that may have some unintended consequences, I decided to use a combination of CSS and HTML to tackle the problem. The use of a few extra HTML tags along with some *hopefully* less intrusive CSS will lessen the chances that I’d screw up the intended purpose of the component I’m trying to add the feature to!

Here are a few benefits that this approach will give us:

  • No JavaScript
  • Does not interfere with the layout/CSS of the menu you’re trying to scroll content for
  • Works with multiple scroll-menu in the same dropdown-menu
  • Works with dropdown-submenu
  • Works in responsive mode and mobile/touch enabled
  • Allows for static headers and footers that will not scroll with the content using the normal list items
  • The scroll-menu will grow dynamically until it reaches the max-height (at which point it will show a vertical scrollbar for each scroll-menu)

Well, I’ll cut to the chase and give you the solution I came up with *because nothings more irritating than trying to search through all the wrong ways someone did something before you find the final solution* (or you can jump to the finalĀ JFiddle example)

CSS

/* So we wont impact the original bootstrap menu
or it's pseudo call-out arrow the menu is wrapped
in a sub dropdown-menu with a chained scroll-menu */
ul.scroll-menu {
    position: relative;
    display: inherit !important;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    -moz-overflow-scrolling: touch;
    -ms-overflow-scrolling: touch;
    -o-overflow-scrolling: touch;
    overflow-scrolling: touch;
    top: 0 !important;
    left: 0 !important;
    width: 100%;
    height: auto;
    max-height: 500px;
    margin: 0;
    border-left: none;
    border-right: none;
    -webkit-border-radius: 0 !important;
    -moz-border-radius: 0 !important;
    -ms-border-radius: 0 !important;
    -o-border-radius: 0 !important;
    border-radius: 0 !important;
    -webkit-box-shadow: none;
    -moz-box-shadow: none;
    -ms-box-shadow: none;
    -o-box-shadow: none;
    box-shadow: none;
}

HTML

<!-- Example of a single scrollable dropdown-menu navigation 	 (notice the placement of the "scroll-menu" class) --></pre>
<ul class="nav">
	<li class="dropdown"><a href="#" data-toggle="dropdown">
 <b class="icon-th-large icon-white"></b>
 </a>
<ul class="dropdown-menu">
<ul class="dropdown-menu"><!-- static non-scrollable menu header --></ul>
</ul>
<ul class="dropdown-menu">
<ul class="dropdown-menu">
	<li class="disabled"><a href="#"><b>My Theme:</b></a></li>
	<li><!-- Here we have our dropdown-menu wrapper so we don't mess 					 with the layout of the outer dropdown-menu -->
<ul class="dropdown-menu scroll-menu">
<ul class="dropdown-menu scroll-menu">
	<li><a href="#"><i class="icon-asterisk"></i> Start</a></li>
	<li><a href="#"><i class="icon-minus"></i> UI Lightness</a></li>
</ul>
</ul>
<ul class="dropdown-menu scroll-menu"><!-- Keep adding more list items and watch how your menu 						 will grow w/o a vertical scollbar until it reaches 						 the max-height set in the scroll-menu class --></ul>
</li>
</ul>
</ul>
<ul class="dropdown-menu">
<ul class="dropdown-menu"><!-- static non-scrollable menu footer --></ul>
</ul>
<ul class="dropdown-menu">
	<li class="disabled"><a href="#">
 <i class="icon-chevron-up pull-left"></i>
 <i class="icon-chevron-up pull-right"></i>
 </a></li>
</ul>
</li>
</ul>
<pre>

Result

scroll-menu1

Now that we have a scroll-menu working that’s decoupled from our main dropdown-menu, lets try to add multiple scroll-menus to our main dropdown-menu (again, you can skip to the complete JFiddle example if you’d like):

HTML

<!-- Example of a multiple scrollable dropdown-menu navigation      (notice the placement of the "scroll-menu" class as well      as the "scroll-menu-2x" class that helps to cut the      max-height down to about 1/2 the size of our normal      scroll-menu max-height) --></pre>
<ul class="nav">
	<li class="dropdown"><a href="#" data-toggle="dropdown">
 <b class="icon-key icon-white"></b>
 </a>
<ul class="dropdown-menu">
<ul class="dropdown-menu"><!-- static non-scrollable menu header 1 --></ul>
</ul>
<ul class="dropdown-menu">
<ul class="dropdown-menu">
	<li class="disabled"><a href="#"><i class="icon-group"></i> <b>My Groups</b></a></li>
	<li>
<ul class="dropdown-menu scroll-menu scroll-menu-2x">
<ul class="dropdown-menu scroll-menu scroll-menu-2x">
	<li><a href="#">User</a></li>
	<li><a href="#">Administrators</a></li>
	<li><a href="#">Some Other Group</a></li>
</ul>
</ul>
<ul class="dropdown-menu scroll-menu scroll-menu-2x"><!-- Additional menu items omitted for brevity --></ul>
</li>
</ul>
</ul>
<ul class="dropdown-menu">
<ul class="dropdown-menu"><!-- static non-scrollable menu header 2 --></ul>
</ul>
<ul class="dropdown-menu">
<ul class="dropdown-menu">
	<li class="disabled"><a href="#"><i class="icon-user"></i> <b>My Roles</b></a></li>
	<li>
<ul class="dropdown-menu scroll-menu scroll-menu-2x">
<ul class="dropdown-menu scroll-menu scroll-menu-2x">
	<li><a href="#">Core Users</a></li>
	<li><a href="#">Admin</a></li>
	<li><a href="#">Some Other Role</a></li>
</ul>
</ul>
<ul class="dropdown-menu scroll-menu scroll-menu-2x"><!-- Additional menu items omitted for brevity --></ul>
</li>
</ul>
</ul>
<ul class="dropdown-menu">
<ul class="dropdown-menu"><!-- static non-scrollable menu footer --></ul>
</ul>
<ul class="dropdown-menu">
	<li class="disabled"><a href="#">
 <i class="icon-chevron-up pull-left"></i>
 <i class="icon-chevron-up pull-right"></i>
 </a></li>
</ul>
</li>
</ul>
<pre>

Result

scroll-menu2

…and last, but not least we have the famed dropdown-submenu. This is where a lot of other solutions either partially work or do not work at all. While the proposed hack I’m suggesting may not be perfect it seems to get us going with a minimal impact on the core bootstrap implementation. Most of the HTML below should look almost identical to the way the normal dropdown-menu / dropdown-submenu is laid out with the addition of an added “dropdown-menu scroll-menu“:

scroll-menu3

Let me know if you find a better solution or have made improvements to the original JFiddle example!

Generating JPA Canonical Model Metadata In Maven

One of the nice features of JPA 2.x is type-safe queries using the Criteria API. Instead of referencing a string or building an endless bounty of static parameters JPA implementations provide a means to generate them. For example:

EntityManager em = ...
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Person> cq = cb.createQuery(Person.class);
Root<Person> at = cq.from(Person.class);
cq.select(at);
// Person_.java is the generated canonical so we don't have to use
// at.get("firstName") and at.get("lastName")
cq.where(cb.and(cb.equal(at.get(Person_.firstName), "John"),
		cb.equal(at.get(Person_.lastName), "Doe")));
TypedQuery<AuditReg> q = em.createQuery(cq);
q.getResultList();

Each JPA implementation has it’s own means for generating canonical model metadata, but most of them provide a javax.annotation.processing.Processor implementation. I’ve seen a few posts demonstrating how to integrate this feature into Maven builds. Most of them use some external Maven plugin to accomplish this, but I found it better to just use the maven-compiler-plugin within a Maven profile. A Maven profile allows us to decouple the projects compilation from the canonical generation so that we aren’t generating canonical metadata every time the project is compiled. This approach makes sense seeing how infrequently the metadata needs to be generated (not to mention it could potentially cause problems using Maven IDE integration tools). It also prevents issues with dependency resolutions. Here is an example using eclipselink:

<build>
	<plugins>
		<plugin>
			<!-- source/target java version needs to be at least 1.6 -->
			<artifactId>maven-compiler-plugin</artifactId>
			<version>3.0</version>
			<configuration>
				<source>1.7</source>
				<target>1.7</target>
			</configuration>
		</plugin>
	</plugins>
</build>
<profiles>
	<profile>
		<id>jpa-canonical-gen</id>
		<build>
			<plugins>
				<plugin>
					<artifactId>maven-compiler-plugin</artifactId>
					<version>3.0</version>
					<configuration>
						<proc combine.self="override">only</proc>
						<!-- compilerArguments can be removed if there is only one -->
						<!-- PU in the project or eclipselink.canonicalmodel.subpackage -->
						<!-- has been defined for each additional PU added in 
						<!-- persistence.xml for more details see 
						<!-- https://bugs.eclipse.org/bugs/show_bug.cgi?id=308713 -->
						<compilerArguments combine.children="append">
							<Aeclipselink.persistenceunits>
								myPuName
							</Aeclipselink.persistenceunits>
						</compilerArguments>
						<!-- edit the unique directory path to match where the -->
						<!-- JPA entities exist -->
						<includes combine.self="override">
							<include>**/entity/jpa/*.java</include>
						</includes>
						<generatedSourcesDirectory>
							${project.build.sourceDirectory}
						</generatedSourcesDirectory>
						<annotationProcessors combine.self="override">
							<annotationProcessor>
								org.eclipse.persistence.internal.jpa.modelgen.CanonicalModelProcessor
							</annotationProcessor>
						</annotationProcessors>
					</configuration>
				</plugin>
			</plugins>
		</build>
		<dependencies>
			<dependency>
				<groupId>org.eclipse.persistence</groupId>
				<artifactId>org.eclipse.persistence.jpa.modelgen</artifactId>
				<version>2.4.0</version>
			</dependency>
		</dependencies>
	</profile>
</profiles>
<dependencies>
	<dependency>
		<groupId>org.eclipse.persistence</groupId>
		<artifactId>org.eclipse.persistence.jpa</artifactId>
		<version>2.4.0</version>
		<scope>compile</scope>
	</dependency>
	<dependency>
		<groupId>org.eclipse.persistence</groupId>
		<artifactId>javax.persistence</artifactId>
		<version>2.0.0</version>
	</dependency>
</dependencies>

The above Maven POM snippet can be executed using the following Maven command and will generate the canonical metadata java source in the same package where the JPA entities exists:

mvn -P jpa-canonical-gen

The example can easily be adapted for other JPA implementations as well by replacing the appropriate project dependencies, profile dependencies (for the annotation processor), and the annotationProcessor with one of the following values:

JavaFX Programmatic POJO Expression Bindings (Part III)

Collection/Map Binding

In Part I and Part II of the JavaFX POJO bindings I introduced possible mechanisms for binding JavaFX control properties to POJOs without having to create all the boilerplate code for the fields of your POJOs. It offered a way to bind POJO fields to JavaFX controls, but excluded POJO fields that contain collections or maps. Since then I have refined the adapter to include bindings that support bindings to/from POJO fields that are of type List, Set, and Map to/from ObservableList, ObservableSet, and ObservableMap. That means you can now bind your POJO fields that carry one of these collection/map types to a ListView, TableView, etc. items and/or their corresponding selections. The source code for the BeanPathAdapter can be downloaded from the JFXtras.org website (or you can view the latest source: BeanPathAdapter/BeanPathAdapterTestApp).

Usage

One of the features of the BeanPathAdapter is that we can bind different JavaFX collection/map types (ObservableList/ObservableSet/ObservableMap) with a different POJO field collection/map types (List/Set/Map). To demo this we’ll stick with the same “Person” example that we used in Part I/II, but this time we’ll add a few fields to demonstrate collection binding. Our POJO will contain Sets that we will use to bind to/from a ListView‘s items (ObservableList) and it’s selectionModel (MultipleSelectionModel). We’ll use the allLanguages/allHobbies fields as the item values for a ListView and the languages/hobbies fields as the selection values of the ListView:

public static class Person {
	...
	private Set languages;
	private Set hobbies;
	private Set allLanguages;
	private Set allHobbies;
	...
	public Set getLanguages() {
		return languages;
	}
	public void setLanguages(Set languages) {
		this.languages = languages;
	}
	public Set getHobbies() {
		return hobbies;
	}
	public void setHobbies(Set hobbies) {
		this.hobbies = hobbies;
	}
	public Set getAllLanguages() {
		return allLanguages;
	}
	public void setAllLanguages(Set allLanguages) {
		this.allLanguages = allLanguages;
	}
	public Set getAllHobbies() {
		return allHobbies;
	}
	public void setAllHobbies(Set allHobbies) {
		this.allHobbies = allHobbies;
	}
}
public static class Hobby {
	private String name;
	private String description;

	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getDescription() {
		return description;
	}
	public void setDescription(String description) {
		this.description = description;
	}
}

The languages will contain simple String values and the hobbies will use a more complex Hobby POJO where we will supply a path to Hobby#name that the adapter will use as the display value of each item in the example ListView. As far as the JavaFX control is concerned it will always contain values of the same (or a coercible counterpart) type as the Hobby#name field.

Person person = new Person();
BeanPathAdapter<Person> personPA = new BeanPathAdapter<>(person);

// initial items will never be seen because the
// POJO bean allHobbies will take presidence
ListView llv = new ListView<>(
		FXCollections.observableArrayList(new String[]{
		"Language Item Never Shown"}));
// bind the items to/from Person#allLanguages
personPA.bindContentBidirectional("allLanguages", null, String.class,
		llv.getItems(), String.class, null);
// we need to pass in the SelectionModel of
// llv.getSelectionModel() so that updates to the
// ReadOnlyUnbackedObservableList can be updated
personPA.bindContentBidirectional("languages", null,
		String.class, llv.getSelectionModel().getSelectedItems(),
		String.class,
		llv.getSelectionModel());

// initial items will never be seen because the
// POJO bean allHobbies will take presidence
ListView hlv = new ListView<>(
		FXCollections.observableArrayList(new String[]{
		"Hobby Item Never Shown"}));
// bind the items to/from Person#allHobbies
personPA.bindContentBidirectional("allHobbies", "name", Hobby.class,
		hlv.getItems(), String.class, null);
// we need to pass in the SelectionModel of
// hlv.getSelectionModel() so that updates to the
// ReadOnlyUnbackedObservableList can be updated
personPA.bindContentBidirectional("hobbies", "name",
		Hobby.class, hlv.getSelectionModel().getSelectedItems(),
		String.class,
		hlv.getSelectionModel());

Now that the items in the ListView are bound to/from the Person#allLanguages/Person#allHobbies fields and the selections for those items are bound to the Person#languages/Person#hobbies fields changes in the controls will be reflected in the corresponding POJO fields. Due to the nature of POJO field change capture (as well as the collection/map) you may need to call personPA.setBean(person); after changes are made DIRECTLY to POJO collections/maps to see them within the JavaFX controls they are bound to. One of the nice things about the decoupled nature of collection/map bindings is that the JavaFX control will always have a primitive value (or a primitive boxed type). This allows you to add/put/remove a primitive value to/from the control while the adapter handles any instantiation/conversion needed to get the primitive value reflected in your POJO’s collection/map. For example, the sample application has a “Add Hobby” button. When clicked it takes the String value of the hobby name entered and calls listView.getItems().add(textField.getText());. The adapter handles the instantiation of a new Hobby, calls newHobby.setName(nameFromTextField), and ultimately calls person.getAllHobbies().add(newHobby). None of which requires any additional steps by the implementer.

Summary of Usage

Here are a few usage examples:

  1. Binding bean fields to multiple JavaFX control properties of different types:

    // Assuming "age" is a double field in person we can bind it to a 
    // Slider#valueProperty() of type double, but we can also bind it
    // to a TextField#valueProperty() of type String. 
    Person person = new Person();
    BeanPathAdapter<Person> personPA = new BeanPathAdapter<>(person);
    Slider sl = new Slider();
    TextField tf = new TextField();
    personPA.bindBidirectional("age", sl.valueProperty());
    personPA.bindBidirectional("age", tf.valueProperty());
    
  2. Binding beans within beans:

    // Binding a bean (Person) field called "address" that contains another 
    // bean (Address) that also contains a field called "location" with a 
    // bean (Location) field of "state" (the chain can be virtually endless
    // with all beans being instantiated along the way when null).
    Person person = new Person();
    BeanPathAdapter<Person> personPA = new BeanPathAdapter<>(person);
    TextField tf = new TextField();
    personPA.bindBidirectional("address.location.state", tf.valueProperty());
    
  3. Binding non-primitive bean paths to JavaFX control properties of the same non-primitive type:

    // Assuming "role" is a "Role" field in a "Person" class we can 
    // bind it to a ComboBox#valueProperty() of the same type. The "Role" 
    // class should override the "toString()" method in order to show a 
    // meaningful selection value in the example ComboBox.
     Role r1 = new Role();
     Role r2 = new Role();
     r1.setName("Administrator");
     r2.setName("User");
     ComboBox<Role> cb = new ComboBox<>();
     cb.getItems().addAll(r1, r2);
     Person person = new Person();
     BeanPathAdapter<Person> personPA = new BeanPathAdapter<>(person);
     personPA.bindBidirectional("role", cb.valueProperty(), Role.class);
    
  4. Binding collections/maps fields to/from observable collections/maps (i.e. items in a JavaFX control):

    // Assuming "allLanguages" is a collection/map field in person we can
    // bind it to a JavaFX observable collection/map
    Person person = new Person();
    BeanPathAdapter<Person> personPA = new BeanPathAdapter<>(person);
    ListView<String> lv = new ListView<>();
    personPA.bindContentBidirectional("allLanguages", null, String.class, lv.getItems(), String.class, null, null);
    
  5. Binding collections/maps fields to/from observable collections/maps selections (i.e. selections in a JavaFX control):

    // Assuming "languages" is a collection/map field in person we can
    // bind it to a JavaFX observable collection/map selections
    Person person = new Person();
    BeanPathAdapter<Person> personPA = new BeanPathAdapter<>(person);
    ListView<String> lv = new ListView<>();
    personPA.bindContentBidirectional("languages", null, String.class, lv.getSelectionModel().getSelectedItems(), String.class, lv.getSelectionModel(), null);
    
  6. Binding collection/map fields to/from observable collections/maps selections using an items from another observable collection/map as a reference (i.e. selections in a JavaFX control that contain the same instances as what are in the items being selected from):

    // Assuming "languages" and "allLanguages" are a collection/map 
    // fields in person we can bind "languages" to selections made from
    // the items in "allLanguages" to a JavaFX observable collection/map 
    // selection
    Person person = new Person();
    BeanPathAdapter<Person> personPA = new BeanPathAdapter<>(person);
    ListView<String> lv = new ListView<>();
    personPA.bindContentBidirectional("languages", null, String.class, lv.getSelectionModel().getSelectedItems(), String.class, lv.getSelectionModel(), "allLanguages");
    
  7. Binding complex bean collection/map fields to/from observable collections/maps selections and items (i.e. selections in a JavaFX control that contain the same bean instances as what are in the items being selected):

    // Assuming "hobbies" and "allHobbies" are a collection/map 
    // fields in person and each element within them contain an 
    // instance of Hobby that has it's own field called "name" 
    // we can bind "allHobbies" and "hobbies" to the Hobby "name"s 
    // for each Hobby in the items/selections (respectively) to/from
    // a ListView wich will only contain the String name of each Hobby
    // as it's items and selections
    Person person = new Person();
    BeanPathAdapter<Person> personPA = new BeanPathAdapter<>(person);
    ListView<String> lv = new ListView<>();
    // bind items
    personPA.bindContentBidirectional("allHobbies", "name", Hobby.class, lv.getItems(), String.class, null, null);
    // bind selections that reference the same instances within the items
    personPA.bindContentBidirectional("hobbies", "name", Hobby.class, lv.getSelectionModel().getSelectedItems(), String.class, lv.getSelectionModel(), "allHobbies");
    
  8. Binding bean collection/map fields to/from multiple JavaFX control observable collections/maps of the same type (via bean collection/map):

    Person person = new Person();
    Hobby hobby1 = new Hobby();
    hobby1.setName("Hobby 1");
    Hobby hobby2 = new Hobby();
    hobby2.setName("Hobby 2");
    person.setAllHobbies(new LinkedHashSet<Hobby>());
    person.getAllHobbies().add(hobby1);
    person.getAllHobbies().add(hobby2);
    BeanPathAdapter<Person> personPA = new BeanPathAdapter<>(person);
    ListView<String> lv = new ListView<>();
    personPA.bindContentBidirectional("allHobbies", "name", Hobby.class, 
    		lv.getItems(), String.class, null, null);
    ListView<String> lv2 = new ListView<>();
    personPA.bindContentBidirectional("allHobbies", "name", Hobby.class, 
    		lv2.getItems(), String.class, null, null);
    
  9. Binding bean collection/map fields to/from multiple JavaFX control observable collections/maps of the same type (via JavaFX control observable collection/map):

    // When the bean collection/map field is empty/null and it is
    // bound to a non-empty observable collection/map, the values
    // of the observable are used to instantiate each item bean
    // and set the item value (Hobby#setName in this case)
    Person person = new Person();
    final ObservableList<String> oc = FXCollections.observableArrayList("Hobby 1", "Hobby 2", "Hobby 3");
    BeanPathAdapter<Person> personPA = new BeanPathAdapter<>(person);
    ListView<String> lv = new ListView<>(oc);
    personPA.bindContentBidirectional("allHobbies", "name", Hobby.class, 
    		lv.getItems(), String.class, null, null);
    ListView<String> lv2 = new ListView<>(); // <-- notice that oc is not passed
    personPA.bindContentBidirectional("allHobbies", "name", Hobby.class, 
    		lv2.getItems(), String.class, null, null);
    
  10. Switching beans:

    // Assuming "age" is a double field in person...
    final Person person1 = new Person();
    person1.setAge(1D);
    final Person person2 = new Person();
    person2.setAge(2D);
    final BeanPathAdapter<Person> personPA = new BeanPathAdapter<>(person1);
    TextField tf = new TextField();
    personPA.bindBidirectional("age", tf.valueProperty());
    Button btn = new Button("Toggle People");
    btn.setOnMouseClicked(new EventHandler<MouseEvent>() {
    	@Override
    	public void handle(MouseEvent event) {
    		// all bindings will show relevant person data and changes made
    		// to the bound controls will be reflected in the bean that is 
    		// set at the time of the change
    		personPA.setBean(personPA.getBean() == person1 ? person2 : person1);
    	}
    });
    
  11. Date/Calendar binding:

    // Assuming "dob" is a java.util.Date or java.util.Calendar field 
    // in person it can be bound to a java.util.Date or 
    // java.util.Calendar JavaFX control property. Example uses a
    // jfxtras.labs.scene.control.CalendarPicker
    final Person person = new Person();
    final BeanPathAdapter<Person> personPA = new BeanPathAdapter<>(person);
    CalendarPicker calendarPicker = new CalendarPicker();
    personPA.bindBidirectional("dob", calendarPicker.calendarProperty(), Calendar.class);
    
  12. TableView items binding:

    // Assuming "name"/"description" are java.lang.String fields 
    // in Hobby and "hobbies" is a List/Set/Map in Person
    final Person person = new Person();
    final BeanPathAdapter<Person> personPA = new BeanPathAdapter<>(person);
    TableView<Hobby> table = new TableView<>();
    TableColumn<Hobby, String> nameCol = new TableColumn<>("Hobby Name");
    nameCol.setMinWidth(100);
    nameCol.setCellValueFactory(
            new PropertyValueFactory<Hobby, String>("name"));
    TableColumn<Hobby, String> descCol = new TableColumn<>("Hobby Desc");
    descCol.setMinWidth(100);
    descCol.setCellValueFactory(
            new PropertyValueFactory<Hobby, String>("description"));
    table.getColumns().addAll(nameCol, descCol);
    personPA.bindContentBidirectional("hobbies", null, String.class,
    	table.getItems(), Hobby.class, null, null);
    
  13. Listening for global changes:

    final Person person = new Person();
    final BeanPathAdapter<Person> personPA = new BeanPathAdapter<>(person);
    // use the following to eliminate unwanted notifications
    // personPA.removeFieldPathValueTypes(FieldPathValueType.BEAN_CHANGE, ...)
    personPA.fieldPathValueProperty().addListener(
     	new ChangeListener<FieldPathValue>() {
     	@Override
     	public void changed(
     		final ObservableValue<? extends FieldPathValue> observable,
     		final FieldPathValue oldValue, final FieldPathValue newValue) {
     			System.out.println("Value changed from: " + oldValue
     				+ " to: " + newValue);
     		}
     	});
    

In Action

Here is some screen shots of the demo application in action (can be downloaded from the JFXtras.org website or view the latest snapshot from: BeanPathAdapter/BeanPathAdapterTestApp):

JavaFX Programmatic POJO Expression Bindings (Part II)

UPDATE: SEE PART III

 

The Ties That Bind

In Part I of the JavaFX POJO bindings I discussed possible solutions to binding JavaFX control properties to POJOs (with automatic POJO property creation). There were huge flaws in the methodology behind my first attempt to get seamless interactions between the two. Since then I have refined my process to take on a more streamlined and seemingly more JavaFX pattern.

The Basic Concept

JavaFX Binding Patterns offer a lot of flexibility, but in a lot of use cases an application can have hundreds of domain model POJOs. That would entail a lot of boiler-plate coding to generate all the properties within those POJOs! I realize that some form of tooling could handle the generation of the POJO to JavaFX property generation, but that would still leave an overwhelmingly abundant amount of classes. For instances where all that is needed is a simple bidirectional binding between POJO fields and JavaFX control properties I found it to be simpler to provide something like the following:

Person person = new Person();
TextField tf = new TextField();
BeanPathAdapter<Person> personPA = new BeanPathAdapter<>(person);
personPA.bindBidirectional("address.location.state", tf.valueProperty());

In the example above I use a path that points to a field within the Person POJO. So, “address.location.state” is synonymous to person.getAddress().getLocation().getState(). Here are a few features that this offers:

  1. Each one of the classes along path traversal is instantiated using a no-argument constructor when it encounters a null child POJO (i.e. person.setArddress(new Address()) and person.getAddress().setLocation(new Location()) are handled)
  2. Multiple bindings between an adapter’s internal POJO properties and different JavaFX control properties are possible
  3. Multiple bindings between an adapter’s internal POJO properties and different JavaFX control property class types are possible (as long as they are convertible)
  4. Changing an adapter’s POJO instance will automatically update any/all of the underlying JavaFX control properties that are bound to them- regardless of how many JavaFX controls are bound to it and regardless of the property value class types of those properties!
  5. All of the associated properties of the POJO and all of the child POJOs are self-contained within one reusable adapter. That makes it easy for an application to only need one BeanPathAdapter for each top-level POJO class (or each instance of a POJO class that needs to be displayed simultaneously)
  6. Utilize Java 7′s new java.lang.invoke.MethodHandle instead of the slower and more memory intensive reflection API

One extra feature that I purposely left off the list is the ability to have a child POJO that contains simple arrays, lists, and maps. It shouldn’t be too difficult to implement, but I will leave that for a later enhancement ;)

Controls of a different feather bind together

As stated, it doesn’t matter how many properties that you bind to a bean path adapter. Nor does it matter what class type the property that your trying to bind to is. It does this by making use of a javafx.util.StringConverter and the type of property that the adapter property is being bound to. In the other direction when the bean field is set it attempts to coerce the value into the type of value it captures from the return type of the accessor method (i.e. getX, isX, hasX where X is the field name). If the value fails to be coerced, say an alphanumeric value exists in a control property, but the bean field is an Integer, then an IllegalArgumentException is thrown. The coercion process allows binding of different property types to be bound to the same bean field property. So, it is perfectly valid to perform the following binding as long as the implementing JavaFX control validates that values are not set that cannot be coerced:

// assuming "age" is a double field in person from previous code snippet
TextField tf = new TextField();
Slider sl = new Slider();
personPA.bindBidirectional("age", tf.valueProperty());
personPA.bindBidirectional("age", sl.valueProperty());

Two things are going on in the code above. First of all, a bean field “age” of type double is being bound to a StringProperty of a TextField and secondly, a DoubleProperty of a Slider is also being bound to the same double field within the adapter. Obviously an additional step would be to limit input in the text field to values of type double (see demo code below for an example of how to do this). The triple bind between the two controls and the auto-generated bean property ensures that all values will coincide with one another in real-time.

Jumping Beans

Another nice feature is that with the same adapter instance you can switch the bean instance out as many times as you want as long as it of the same class type. As the bean changes instances all of the underlying properties and their bound control property counterparts will all be updated at the same time and reflected in the UI. So, using the Person scenario you could potentially have a list/table of persons. When a person clicks on any one of them, a form with one set of UI controls for a Person can be updated with one simple call to personPA.setBean(selectedPersonPOJO).

The Bean Adapter In Action

Here is a quick video demo of it working in action:

Demo Code (Tested with JavaFX 2.1.1)

The demo code uses a simple ComboBox control that contains three different Person instances. As the switches between them all the bound controls update with the selected Persons data. As the data is updated by the user via the UI controls the bean fields immediately reflect those changes. To demonstrate that that is occurring a TextArea is updated with the bean’s fields as they are updated. The right side of the SplitPane contains an unbound TextField used to directly update the bean by circumventing the adapter. While all the bean field(s) are updated as expected, the UI is unaware that the change occurred. Thus, the displayed values are not reflected until the next time the UI is refreshed. See the In A Bind section for more details.

import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Control;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.control.SplitPane;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.ToolBar;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;

public class BeanPathAdapterTest extends Application {

	ChoiceBox<String> pBox;
	TextArea pojoTA = new TextArea();
	public static final String[] STATES = new String[] { "AK", "AL", "AR",
			"AS", "AZ", "CA", "CO", "CT", "DC", "DE", "FL", "GA", "GU", "HI",
			"IA", "ID", "IL", "IN", "KS", "KY", "LA", "MA", "MD", "ME", "MH",
			"MI", "MN", "MO", "MS", "MT", "NC", "ND", "NE", "NH", "NJ", "NM",
			"NV", "NY", "OH", "OK", "OR", "PA", "PR", "PW", "RI", "SC", "SD",
			"TN", "TX", "UT", "VA", "VI", "VT", "WA", "WI", "WV", "WY" };
	private static final String P1_LABEL = "Person 1 (initially has data)";
	private static final String P2_LABEL = "Person 2 (initially no data)";
	private static final String P3_LABEL = "Person 3 (initially no data)";
	private final Person person1 = new Person();
	private final Person person2 = new Person();
	private final Person person3 = new Person();
	private final BeanPathAdapter<Person> personPA = new BeanPathAdapter<>(
			person1);

	public static void main(final String[] args) {
		Application.launch(BeanPathAdapterTest.class, args);
	}

	public BeanPathAdapterTest() {
		super();
		person1.setAge(50d);
		person1.setName("Person 1");
		Address addy = new Address();
		Location loc = new Location();
		loc.setCountry(1);
		loc.setInternational(true);
		loc.setState("KY");
		addy.setStreet("123 Test Street");
		addy.setLocation(loc);
		person1.setAddress(addy);
	}

	@Override
	public void start(Stage primaryStage) throws Exception {
		primaryStage.setTitle(BeanPathAdapter.class.getSimpleName() + " TEST");
		pojoTA.setFocusTraversable(false);
		pojoTA.setWrapText(true);
		pojoTA.setEditable(false);
		pBox = new ChoiceBox<>(FXCollections.observableArrayList(P1_LABEL,
				P2_LABEL, P3_LABEL));
		pBox.getSelectionModel().select(0);
		pBox.valueProperty().addListener(new ChangeListener<String>() {
			@Override
			public void changed(ObservableValue<? extends String> observable,
					String oldValue, String newValue) {
				personPA.setBean(newValue == P1_LABEL ? person1
						: newValue == P2_LABEL ? person2 : person3);
			}
		});
		pBox.autosize();
		ToolBar toolBar = new ToolBar();
		toolBar.getItems().add(pBox);
		VBox personBox = new VBox(10);
		personBox.setPadding(new Insets(10, 10, 10, 50));
		VBox beanPane = new VBox(10);
		beanPane.setPadding(new Insets(10, 10, 10, 10));
		final Text title = new Text(
				"Person POJO using auto-generated JavaFX properties. "
						+ "Duplicate field controls exist to demo multiple control binding");
		title.setWrappingWidth(400d);
		personBox.getChildren().addAll(
				beanTF("name", 50, null, "[a-zA-z0-9\\s]*"),
				beanTF("age", 100, Slider.class, null),
				beanTF("age", 100, null, "[0-9]"),
				beanTF("address.street", 50, null, "[a-zA-z0-9\\s]*"),
				beanTF("address.location.state", 2, ComboBox.class, "[a-zA-z]",
						STATES),
				beanTF("address.location.country", 10, null, "[0-9]"),
				beanTF("address.location.country", 2, ComboBox.class, "[0-9]",
						new Integer[]{0, 1, 2, 3}),
				beanTF("address.location.international", 0, CheckBox.class,
						null), new Label("POJO Dump:"), pojoTA);
		beanPane.getChildren().addAll(title, personBox);

		final TextField pojoNameTF = new TextField();
		Button pojoNameBtn = new Button("Set Person's Name");
		pojoNameBtn.setOnMouseClicked(new EventHandler<MouseEvent>() {
			@Override
			public void handle(MouseEvent event) {
				personPA.getBean().setName(pojoNameTF.getText());
				dumpPojo(personPA);
			}
		});
		VBox pojoBox = new VBox(10);
		pojoBox.setPadding(new Insets(10, 10, 10, 10));
		Text lbl = new Text("Set selected person's field data via POJO "
				+ "with unbound controls (not working because without "
				+ "dependency injection instrumentation, java agent, or "
				+ "byte-code manipulation this is not currently possible- "
				+ "maybe a JavaFX life-cycle listener would work?):");
		lbl.setWrappingWidth(300d);
		pojoBox.getChildren().addAll(lbl, new Label("Name:"), pojoNameTF,
				pojoNameBtn);

		SplitPane pojoSplit = new SplitPane();
		pojoSplit.getItems().addAll(beanPane, pojoBox);
		VBox beanBox = new VBox(10);
		beanBox.getChildren().addAll(toolBar, pojoSplit);
		primaryStage.setOnShowing(new EventHandler<WindowEvent>() {
			@Override
			public void handle(WindowEvent event) {
				dumpPojo(personPA);
			}
		});
		primaryStage.setScene(new Scene(beanBox));
		primaryStage.show();
	}

	@SafeVarargs
	public final void dumpPojo(final BeanPathAdapter<Person>... ps) {
		Platform.runLater(new Runnable() {
			@Override
			public void run() {
				String dump = "";
				for (BeanPathAdapter<Person> p : ps) {
					dump += "Person {name="
							+ p.getBean().getName()
							+ ", age="
							+ p.getBean().getAge()
							+ ", address.street="
							+ p.getBean().getAddress().getStreet()
							+ ", address.location.state="
							+ p.getBean().getAddress().getLocation().getState()
							+ ", address.location.country="
							+ p.getBean().getAddress().getLocation()
									.getCountry()
							+ ", address.location.international="
							+ p.getBean().getAddress().getLocation()
									.isInternational() + "}\n";
				}
				pojoTA.setText(dump);
			}
		});
	}

	@SuppressWarnings("unchecked")
	public <T> HBox beanTF(String path, final int maxChars,
			Class<? extends Control> controlType, 
			final String restictTo, T... choices) {
		HBox box = new HBox();
		Control ctrl;
		if (controlType == CheckBox.class) {
			CheckBox cb = new CheckBox();
			cb.selectedProperty().addListener(new ChangeListener<Boolean>() {
				@Override
				public void changed(
						ObservableValue<? extends Boolean> observable,
						Boolean oldValue, Boolean newValue) {
					dumpPojo(personPA);
				}
			});
			// POJO binding magic...
			personPA.bindBidirectional(path, cb.selectedProperty());
			ctrl = cb;
		} else if (controlType == ComboBox.class) {
			ComboBox<T> cb = new ComboBox<>(
					FXCollections.observableArrayList(choices));
			cb.setPromptText("Select State");
			cb.setPrefWidth(100d);
			cb.valueProperty().addListener(new InvalidationListener() {
				@Override
				public void invalidated(Observable observable) {
					dumpPojo(personPA);
				}
			});
			// POJO binding magic (due to erasure of T in 
			// ObjectProperty<T> of cb.valueProperty() we need
			// to also pass in the choice class
			personPA.bindBidirectional(path, cb.valueProperty(), 
					(Class<T>) choices[0].getClass());
			ctrl = cb;
		} else if (controlType == Slider.class) {
			Slider sl = new Slider();
			sl.setShowTickLabels(true);
			sl.setShowTickMarks(true);
			sl.setMajorTickUnit(maxChars / 2);
			sl.setMinorTickCount(7);
			sl.setBlockIncrement(1);
			sl.setMax(maxChars + 1);
			sl.setSnapToTicks(true);
			sl.valueProperty().addListener(new InvalidationListener() {
				@Override
				public void invalidated(Observable observable) {
					dumpPojo(personPA);
				}
			});
			// POJO binding magic...
			personPA.bindBidirectional(path, sl.valueProperty());
			ctrl = sl;
		} else {
			final TextField tf = new TextField() {
				@Override
				public void replaceText(int start, int end, String text) {
					if (matchTest(text)) {
						super.replaceText(start, end, text);
					}
				}

				@Override
				public void replaceSelection(String text) {
					if (matchTest(text)) {
						super.replaceSelection(text);
					}
				}

				private boolean matchTest(String text) {
					return text.isEmpty()
							|| (text.matches(restictTo) && (getText() == null || getText()
									.length() < maxChars));
				}
			};
			tf.textProperty().addListener(new ChangeListener<String>() {
				@Override
				public void changed(
						ObservableValue<? extends String> observable,
						String oldValue, String newValue) {
					dumpPojo(personPA);
				}
			});
			// POJO binding magic...
			personPA.bindBidirectional(path, tf.textProperty());
			ctrl = tf;
		}
		box.getChildren().addAll(new Label(path + " = "), ctrl);
		return box;
	}

	public HBox beanTFW(String startLabel, String endLabel, TextField... tfs) {
		HBox box = new HBox();
		box.getChildren().add(new Label(startLabel + '('));
		box.getChildren().addAll(tfs);
		box.getChildren().add(new Label(endLabel + ");"));
		return box;
	}

	public static class Person {
		private String name;
		private Address address;
		private double age;

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public Address getAddress() {
			return address;
		}

		public void setAddress(Address address) {
			this.address = address;
		}

		public double getAge() {
			return age;
		}

		public void setAge(double age) {
			this.age = age;
		}
	}

	public static class Address {
		private String street;
		private Location location;

		public String getStreet() {
			return street;
		}

		public void setStreet(String street) {
			this.street = street;
		}

		public Location getLocation() {
			return location;
		}

		public void setLocation(Location location) {
			this.location = location;
		}
	}

	public static class Location {
		private int country;
		private String state;
		private Boolean isInternational;

		public int getCountry() {
			return country;
		}

		public void setCountry(int country) {
			this.country = country;
		}

		public String getState() {
			return state;
		}

		public void setState(String state) {
			this.state = state;
		}

		public Boolean isInternational() {
			return isInternational;
		}

		public void setInternational(Boolean isInternational) {
			this.isInternational = isInternational;
		}
	}

}

Full Source Code (Tested with JavaFX 2.1.1)

Below is the full source code for the bean path adapter complete with documentation…

import java.io.Serializable;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.FloatProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ListProperty;
import javafx.beans.property.LongProperty;
import javafx.beans.property.MapProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.beans.property.Property;
import javafx.beans.property.StringProperty;
import javafx.util.StringConverter;

/**
 * An adapter that takes a POJO bean and internally and recursively
 * binds/un-binds it's fields to other {@linkplain Property} components. It
 * allows a <b><code>.</code></b> separated field path to be traversed on a bean
 * until the final field name is found (last entry in the <b><code>.</code></b>
 * separated field path). Each field will have a corresponding
 * {@linkplain Property} that is automatically generated and reused in the
 * binding process. Each {@linkplain Property} is bean-aware and will
 * dynamically update it's values and bindings as different beans are set on the
 * adapter. Bean's set on the adapter do not need to instantiate all the
 * sub-beans in the path(s) provided as long as they contain a no-argument
 * constructor they will be instantiated as path(s) are traversed.
 * 
 * <p>
 * For example, assume there is a <code>Person</code> class that has a field for
 * an <code>Address</code> class which in turn has a field for a
 * <code>city</code>.
 * <p>
 * If the <code>city</code> field needs to be bound to a JavaFX control
 * {@linkplain Property} for UI updates/viewing it can be accomplished in the
 * following manner:
 * </p>
 * <p>
 * <code>TextField tf = new TextField();<br/>
 * Person person = new Person();<br/>
 * BeanPathAdapter<Person> pbpa = new BeanPathAdapter<Person>(person);<br/> 
 * pbpa.bindBidirectional("address.city", tf.textProperty());<br/> 
 * </code>
 * </p>
 * </p>
 * 
 * @see #bindBidirectional(String, Property)
 * @param <B>
 *            the bean type
 */
public class BeanPathAdapter<B> {

	private FieldBean<Void, B> root;

	/**
	 * Constructor
	 * 
	 * @param bean
	 *            the bean the {@linkplain BeanPathAdapter} is for
	 */
	public BeanPathAdapter(final B bean) {
		setBean(bean);
	}

	/**
	 * @see #bindBidirectional(String, Property, Class)
	 */
	public void bindBidirectional(final String fieldPath,
			final BooleanProperty property) {
		bindBidirectional(fieldPath, property, Boolean.class);
	}

	/**
	 * @see #bindBidirectional(String, Property, Class)
	 */
	public void bindBidirectional(final String fieldPath,
			final StringProperty property) {
		bindBidirectional(fieldPath, property, String.class);
	}

	/**
	 * @see #bindBidirectional(String, Property, Class)
	 */
	public void bindBidirectional(final String fieldPath,
			final Property<Number> property) {
		bindBidirectional(fieldPath, property, null);
	}

	/**
	 * Binds a {@linkplain Property} by traversing the bean's field tree
	 * 
	 * @see FieldBean#bidirectionalBindOperation(String, Property, boolean)
	 * @param fieldPath
	 *            the <b><code>.</code></b> separated field paths relative to
	 *            the {@linkplain #getBean()} that will be traversed
	 * @param property
	 *            the {@linkplain Property} to bind to the field class type of
	 *            the property
	 * @param propertyType
	 *            the class type of the {@linkplain Property} value
	 */
	@SuppressWarnings("unchecked")
	public <T> void bindBidirectional(final String fieldPath,
			final Property<T> property, final Class<T> propertyType) {
		Class<T> clazz = propertyType != null ? propertyType : 
			propertyValueClass(property);
		if (clazz == null && property.getValue() != null) {
			clazz = (Class<T>) property.getValue().getClass();
		}
		if (clazz == null || clazz == Object.class) {
			throw new UnsupportedOperationException(String.format(
					"Unable to determine property value class for %1$s " + 
					"and declared type %2$s", property, propertyType));
		}
		getRoot().bidirectionalBindOperation(fieldPath, property, clazz,
				false);
	}

	/**
	 * Unbinds a {@linkplain Property} by traversing the bean's field tree
	 * 
	 * @see FieldBean#bidirectionalBindOperation(String, Property, boolean)
	 * @param fieldPath
	 *            the <b><code>.</code></b> separated field paths relative to
	 *            the {@linkplain #getBean()} that will be traversed
	 * @param property the {@linkplain Property} to bind to
	 *            the field class type of the property
	 */
	public <T> void unBindBidirectional(final String fieldPath, final Property<T> property) {
		getRoot().bidirectionalBindOperation(fieldPath, property, null, true);
	}

	/**
	 * @return the bean of the {@linkplain BeanPathAdapter}
	 */
	public B getBean() {
		return getRoot().getBean();
	}
	
	/**
	 * Sets the root bean of the {@linkplain BeanPathAdapter}. Any existing
	 * properties will be updated with the values relative to the paths within
	 * the bean.
	 * 
	 * @param bean
	 *            the bean to set
	 */
	public void setBean(final B bean) {
		if (bean == null) {
			throw new NullPointerException();
		}
		if (getRoot() == null) {
			this.root = new FieldBean<>(null, bean, null);
		} else {
			getRoot().setBean(bean);
		}
	}

	/**
	 * @return the root/top level {@linkplain FieldBean}
	 */
	protected final FieldBean<Void, B> getRoot() {
		return this.root;
	}

	/**
	 * Provides the underlying value class for a given {@linkplain Property}
	 * 
	 * @param property
	 *            the {@linkplain Property} to check
	 * @return the value class of the {@linkplain Property}
	 */
	@SuppressWarnings("unchecked")
	protected static <T> Class<T> propertyValueClass(final Property<T> property) {
		Class<T> clazz = null;
		if (property != null) {
			if (StringProperty.class.isAssignableFrom(property.getClass())) {
				clazz = (Class<T>) String.class;
			} else if (IntegerProperty.class.isAssignableFrom(property
					.getClass())) {
				clazz = (Class<T>) Integer.class;
			} else if (BooleanProperty.class.isAssignableFrom(property
					.getClass())) {
				clazz = (Class<T>) Boolean.class;
			} else if (DoubleProperty.class.isAssignableFrom(property
					.getClass())) {
				clazz = (Class<T>) Double.class;
			} else if (FloatProperty.class.isAssignableFrom(property
					.getClass())) {
				clazz = (Class<T>) Float.class;
			} else if (LongProperty.class.isAssignableFrom(property
					.getClass())) {
				clazz = (Class<T>) Long.class;
			} else if (ListProperty.class.isAssignableFrom(property
					.getClass())) {
				clazz = (Class<T>) List.class;
			} else if (MapProperty.class.isAssignableFrom(property
					.getClass())) {
				clazz = (Class<T>) Map.class;
			} else {
				clazz = (Class<T>) Object.class;
			}
		}
		return clazz;
	}

	/**
	 * A POJO bean extension that allows binding based upon a <b><code>.</code>
	 * </b> separated field path that will be traversed on a bean until the
	 * final field name is found. Each bean may contain child
	 * {@linkplain FieldBean}s when
	 * {@linkplain #bidirectionalBindOperation(String, Property, boolean)} is
	 * called with a direct descendant field that is a non-primitive type. Any
	 * primitive types are added as a {@linkplain FieldProperty} reference to
	 * the {@linkplain FieldBean}.
	 * 
	 * @see #bidirectionalBindOperation(String, Property, boolean)
	 * @param <PT>
	 *            the parent bean type
	 * @param <BT>
	 *            the bean type
	 */
	protected static class FieldBean<PT, BT> implements Serializable {

		private static final long serialVersionUID = 7397535724568852021L;
		private final Map<String, FieldBean<BT, ?>> fieldBeans = new HashMap<>();
		private final Map<String, FieldProperty<BT, ?>> fieldProperties = new HashMap<>();
		private final Map<Class<?>, FieldStringConverter<?>> stringConverters = new HashMap<>();
		private FieldHandle<PT, BT> fieldHandle;
		private final FieldBean<?, PT> parent;
		private BT bean;

		/**
		 * Creates a {@linkplain FieldBean}
		 * 
		 * @param parent
		 *            the parent {@linkplain FieldBean} (should not be null)
		 * @param fieldHandle
		 *            the {@linkplain FieldHandle} (should not be null)
		 */
		protected FieldBean(final FieldBean<?, PT> parent, final FieldHandle<PT, BT> fieldHandle) {
			this.parent = parent;
			this.fieldHandle = fieldHandle;
			this.bean = this.fieldHandle.setDerivedValueFromAccessor();
			if (getParent() != null) {
				getParent().addFieldBean(this);
			}
		}

		/**
		 * Creates a {@linkplain FieldBean} with a generated
		 * {@linkplain FieldHandle} that targets the supplied bean and is
		 * projected on the parent {@linkplain FieldBean}. It assumes that the
		 * supplied {@linkplain FieldBean} has been set on the parent
		 * {@linkplain FieldBean}.
		 * 
		 * @see #createFieldHandle(Object, Object, String)
		 * @param parent
		 *            the parent {@linkplain FieldBean} (null when it's the root)
		 * @param bean
		 *            the bean that the {@linkplain FieldBean} is for
		 * @param fieldName
		 *            the field name of the parent {@linkplain FieldBean} for
		 *            which the new {@linkplain FieldBean} is for
		 */
		protected FieldBean(final FieldBean<?, PT> parent, final BT bean, final String fieldName) {
			if (bean == null) {
				throw new NullPointerException("Bean cannot be null");
			}
			this.parent = parent;
			this.bean = bean;
			this.fieldHandle = getParent() != null ? createFieldHandle(getParent().getBean(), 
					bean, fieldName) : null;
			if (getParent() != null) {
				getParent().addFieldBean(this);
			}
		}

		/**
		 * Generates a {@linkplain FieldHandle} that targets the supplied bean
		 * and is projected on the parent {@linkplain FieldBean} that has
		 * 
		 * @param parentBean
		 *            the parent bean
		 * @param bean
		 *            the child bean
		 * @param fieldName
		 *            the field name of the child within the parent
		 * @return the {@linkplain FieldHandle}
		 */
		@SuppressWarnings("unchecked")
		protected FieldHandle<PT, BT> createFieldHandle(final PT parentBean, final BT bean, 
				final String fieldName) {
			return new FieldHandle<PT, BT>(
					parentBean, fieldName, (Class<BT>) getBean().getClass());
		}

		/**
		 * Adds a child {@linkplain FieldBean} if it doesn't already exist.
		 * NOTE: It does <b>NOT</b> ensure the child bean has been set on the
		 * parent.
		 * 
		 * @param fieldBean
		 *            the {@linkplain FieldBean} to add
		 */
		protected void addFieldBean(final FieldBean<BT, ?> fieldBean) {
			if (!getFieldBeans().containsKey(fieldBean.getFieldName())) {
				getFieldBeans().put(fieldBean.getFieldName(), fieldBean);
			}
		}

		/**
		 * Adds or updates a child {@linkplain FieldProperty}. When the child
		 * already exists it will {@linkplain FieldProperty#setTarget(Object)}
		 * using the bean of the {@linkplain FieldProperty}.
		 * 
		 * @param fieldProperty
		 *            the {@linkplain FieldProperty} to add or update
		 */
		protected void addOrUpdateFieldProperty(final FieldProperty<BT, ?> fieldProperty) {
			final String pkey = fieldProperty.getName();
			if (getFieldProperties().containsKey(pkey)) {
				getFieldProperties().get(pkey).setTarget(fieldProperty.getBean());
			} else {
				getFieldProperties().put(pkey, fieldProperty);
			}
		}

		/**
		 * @see #setParentBean(Object)
		 * @return the bean that the {@linkplain FieldBean} represents
		 */
		public BT getBean() {
			return bean;
		}
		
		/**
		 * Sets the bean of the {@linkplain FieldBean} and it's underlying
		 * {@linkplain #getFieldNodes()} and {@linkplain #getFieldProperties()}
		 * 
		 * @see #setParentBean(Object)
		 * @param bean
		 */
		public void setBean(final BT bean) {
			if (bean == null) {
				throw new NullPointerException("Bean cannot be null");
			}
			this.bean = bean;
			for (final Map.Entry<String, FieldBean<BT, ?>> fn : getFieldBeans().entrySet()) {
				fn.getValue().setParentBean(getBean());
			}
			for (final Map.Entry<String, FieldProperty<BT, ?>> fp : getFieldProperties().entrySet()) {
				fp.getValue().setTarget(getBean());
			}
		}
		
		/**
		 * Binds a parent bean to the {@linkplain FieldBean} and it's underlying
		 * {@linkplain #getFieldNodes()} and {@linkplain #getFieldProperties()}
		 * 
		 * @see #setBean(Object)
		 * @param bean
		 *            the parent bean to bind to
		 */
		public void setParentBean(final PT bean) {
			if (bean == null) {
				throw new NullPointerException("Cannot bind to a null bean");
			} else if (fieldHandle == null) {
				throw new IllegalStateException("Cannot bind to a root " + 
						FieldBean.class.getSimpleName());
			}
			fieldHandle.setTarget(bean);
			setBean(fieldHandle.setDerivedValueFromAccessor());
		}

		/**
		 * Binds/Unbinds a {@linkplain FieldProperty} based upon the supplied
		 * <b><code>.</code></b> separated path to the field by traversing the
		 * matching children of the {@linkplain FieldBean} until the
		 * corresponding {@linkplain FieldProperty} is found (target bean uses
		 * the POJO from {@linkplain FieldBean#getBean()}). If the operation is
		 * bind and the {@linkplain FieldProperty} doesn't exist all relative
		 * {@linkplain FieldBean}s in the path will be instantiated using a
		 * no-argument constructor until the {@linkplain FieldProperty} is
		 * created and bound to the supplied {@linkplain Property}. The process
		 * is reciprocated until all path {@linkplain FieldBean} and
		 * {@linkplain FieldProperty} attributes of the field path are
		 * extinguished.
		 * 
		 * @see Bindings#bindBidirectional(Property, Property)
		 * @see Bindings#unbindBidirectional(Property, Property)
		 * @param fieldPath
		 *            the <code>.</code> separated field names
		 * @param property
		 *            the {@linkplain Property} to bind/unbind
		 * @param propertyValueClass
		 *            the class of the {@linkplain Property} value type (only
		 *            needed when binding)
		 * @param unbind
		 *            true to unbind, false to bind
		 */
		@SuppressWarnings("unchecked")
		public <T> void bidirectionalBindOperation(final String fieldPath,
				final Property<T> property, final Class<T> propertyValueClass,
				final boolean unbind) {
			final String[] fieldNames = fieldPath.split("\\.");
			final boolean isProperty = fieldNames.length == 1;
			final String pkey = isProperty ? fieldNames[0] : "";
			if (isProperty && getFieldProperties().containsKey(pkey)) {
				final FieldProperty<BT, ?> fp = getFieldProperties().get(pkey);
				if (unbind) {
					Bindings.unbindBidirectional((Property<T>) fp, property);
				} else {
					// because of the inverse relationship of the bidirectional
					// bind the initial value needs to be captured and reset as
					// a dirty value or the bind operation will overwrite the
					// initial value with the value of the passed property
					final Object val = fp.getDirty();
					Bindings.bindBidirectional((Property<String>) fp, property,
							(StringConverter<T>) getFieldStringConverter(
									propertyValueClass));
					if (val != null && !val.toString().isEmpty()) {
						fp.setDirty(val);
					}
				}
			} else if (!isProperty
					&& getFieldBeans().containsKey(fieldNames[0])) {
				// progress to the next child field/bean in the path chain
				final String nextFieldPath = fieldPath.substring(fieldPath
						.indexOf(fieldNames[1]));
				getFieldBeans().get(fieldNames[0]).bidirectionalBindOperation(
						nextFieldPath, property, propertyValueClass, unbind);
			} else if (!unbind) {
				// add a new bean/property chain
				if (isProperty) {
					final FieldProperty<BT, ?> childProp = new FieldProperty<>(
							getBean(), fieldNames[0], Object.class);
					addOrUpdateFieldProperty(childProp);
					bidirectionalBindOperation(fieldNames[0], property, 
							propertyValueClass, unbind);
				} else {
					// create a handle to set the bean as a child of the current
					// bean
					// if the child bean exists on the bean it will remain
					// unchanged
					final FieldHandle<BT, Object> pfh = new FieldHandle<>(
							getBean(), fieldNames[0], Object.class);
					final FieldBean<BT, ?> childBean = new FieldBean<>(this,
							pfh);
					// progress to the next child field/bean in the path chain
					final String nextFieldPath = fieldPath.substring(fieldPath
							.indexOf(fieldNames[1]));
					childBean.bidirectionalBindOperation(nextFieldPath,
							property, propertyValueClass, unbind);
				}
			}
		}

		/**
		 * @return the field name that the {@linkplain FieldBean} represents in
		 *         it's parent (null when the {@linkplain FieldBean} is root)
		 */
		public String getFieldName() {
			return fieldHandle != null ? fieldHandle.getFieldName() : null;
		}
		
		/**
		 * Determines if the {@linkplain FieldBean} contains a field with the
		 * specified name
		 * 
		 * @param fieldName
		 *            the field name to check for
		 * @return true when the field exists
		 */
		public boolean hasField(final String fieldName) {
			return getFieldBeans().containsKey(fieldName)
					|| getFieldProperties().containsKey(
							getFieldProperties().get(fieldName));
		}

		/**
		 * @return the parent {@linkplain FieldBean} (null when the
		 *         {@linkplain FieldBean} is root)
		 */
		public FieldBean<?, PT> getParent() {
			return parent;
		}

		/**
		 * @see #getFieldProperties()
		 * @return the {@linkplain Map} of fields that belong to the
		 *         {@linkplain FieldBean} that are not a
		 *         {@linkplain FieldProperty}, but rather exist as a
		 *         {@linkplain FieldBean} that may or may not contain their own
		 *         {@linkplain FieldProperty} instances
		 */
		protected Map<String, FieldBean<BT, ?>> getFieldBeans() {
			return fieldBeans;
		}

		/**
		 * @see #getFieldBeans()
		 * @return the {@linkplain Map} of fields that belong to the
		 *         {@linkplain FieldBean} that are not {@linkplain FieldBean}s,
		 *         but rather exist as a {@linkplain FieldProperty}
		 */
		protected Map<String, FieldProperty<BT, ?>> getFieldProperties() {
			return fieldProperties;
		}

		/**
		 * Gets/Creates (if not already created) a
		 * {@linkplain FieldStringConverter}.
		 * 
		 * @param targetClass
		 *            the target class of the {@linkplain FieldStringConverter}
		 * @return the {@linkplain FieldStringConverter}
		 */
		@SuppressWarnings("unchecked")
		public <FCT> FieldStringConverter<FCT> getFieldStringConverter(final Class<FCT> targetClass) {
			if (stringConverters.containsKey(targetClass)) {
				return (FieldStringConverter<FCT>) stringConverters.get(targetClass);
			} else {
				final FieldStringConverter<FCT> fsc = new FieldStringConverter<>(targetClass);
				stringConverters.put(targetClass, fsc);
				return fsc;
			}
		}
	}

	/**
	 * Coercible {@linkplain StringConverter} that handles conversions between
	 * strings and a target class when used in the binding process
	 * {@linkplain Bindings#bindBidirectional(Property, Property, StringConverter)}
	 * 
	 * @see StringConverter
	 * @param <T>
	 *            the target class type that is used in the coercion of the
	 *            string
	 */
	protected static class FieldStringConverter<T> extends StringConverter<T> {

		private final Class<T> targetClass;

		/**
		 * Constructor
		 * 
		 * @param targetClass
		 *            the class that the {@linkplain FieldStringConverter} is
		 *            targeting
		 */
		public FieldStringConverter(final Class<T> targetClass) {
			this.targetClass = targetClass;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public T fromString(final String string) {
			return coerce(string, targetClass);
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public String toString(final T object) {
			return object != null ? object.toString() : null;
		}

		/**
		 * @return the target class that is used in the coercion of the string
		 */
		public Class<T> getTargetClass() {
			return targetClass;
		}

		/**
		 * Attempts to coerce a value into the specified class
		 * 
		 * @param v
		 *            the value to coerce
		 * @param targetClass
		 *            the class to coerce to
		 * @return the coerced value (null when value failed to be coerced)
		 */
		@SuppressWarnings("unchecked")
		public static <VT> VT coerce(final Object v, final Class<VT> targetClass) {
			if (targetClass == Object.class) {
				return (VT) v;
			}
			VT val;
			final boolean isStringType = targetClass.equals(
					String.class);
			if (v == null || (!isStringType && v.toString().isEmpty())) {
				val = (VT) FieldHandle.defaultValue(targetClass);
			} else if (isStringType
					|| (v != null && v.getClass().isAssignableFrom(
							targetClass))) {
				val = (VT) targetClass.cast(v);
			} else {
				val = FieldHandle.valueOf(targetClass, v.toString());
			}
			return val;
		}
	}
	
	/**
	 * A {@linkplain Property} extension that uses a bean's getter/setter to
	 * define the {@linkplain Property}'s value.
	 * 
	 * @param <BT>
	 *            the bean type
	 * @param <T>
	 *            the field type
	 */
	protected static class FieldProperty<BT, T> extends ObjectPropertyBase<String> {
		
		private final FieldHandle<BT, T> fieldHandle;
		private boolean isDirty;

		/**
		 * Constructor
		 * 
		 * @param bean
		 *            the bean that the path belongs to
		 * @param fieldName
		 *            the name of the field within the bean
		 * @param fieldType
		 *            the {@linkplain Class} of the field
		 */
		protected FieldProperty(final BT bean, final String fieldName,
				final Class<T> fieldType) {
			super();
			this.fieldHandle = new FieldHandle<BT, T>(bean, fieldName,
					fieldType);
			setDerived();
		}
		
		protected void setDerived() {
			final T derived = fieldHandle.deriveValueFromAccessor();
			set(derived != null ? derived.toString() : null);
		}

		/**
		 * Flags the {@linkplain Property} value as dirty and calls
		 * {@linkplain #set(String)}
		 * 
		 * @param v
		 *            the value to set
		 */
		public void setDirty(final Object v) {
			isDirty = true;
			set(v != null ? v.toString() : null);
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void set(final String v) {
			try {
				// final MethodHandle mh2 = MethodHandles.insertArguments(
				// fieldHandle.getSetter(), 0, v);
				final Object cv = fieldHandle.getAccessor().invoke();
				if (!isDirty && v == cv) {
					return;
				}
				final Object val = FieldStringConverter.coerce(v, cv != null ? cv.getClass()
						: fieldHandle.getFieldType());
				fieldHandle.getSetter().invoke(val);
				invalidated();
				fireValueChangedEvent();
				isDirty = false;
			} catch (final Throwable t) {
				throw new IllegalArgumentException("Unable to set value: " + v,
						t);
			}
		};

		/**
		 * {@inheritDoc}
		 */
		@Override
		public String get() {
			try {
				final Object dv = getDirty();
				return dv != null ? dv.toString() : null;
			} catch (final Throwable t) {
				throw new RuntimeException("Unable to get value", t);
			}
		}
		
		/**
		 * @return the dirty value before conversion takes place
		 */
		public Object getDirty() {
			try {
				return fieldHandle.getAccessor().invoke();
			} catch (final Throwable t) {
				throw new RuntimeException("Unable to get dirty value", t);
			}
		}

		/**
		 * Binds a new target to the {@linkplain FieldHandle}
		 * 
		 * @param target
		 *            the target to bind to
		 */
		public void setTarget(final BT bean) {
			isDirty = true;
			fieldHandle.setTarget(bean);
			setDerived();
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public BT getBean() {
			return fieldHandle.getTarget();
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public String getName() {
			return fieldHandle.getFieldName();
		}
	}

	/**
	 * Field handle to {@linkplain FieldHandle#getAccessor()} and
	 * {@linkplain FieldHandle#getSetter()} for a given
	 * {@linkplain FieldHandle#getTarget()}.
	 * 
	 * @param <T>
	 *            the {@linkplain FieldHandle#getTarget()} type
	 * @param <F>
	 *            the {@linkplain FieldHandle#getDeclaredFieldType()} type
	 */
	protected static class FieldHandle<T, F> {

		private static final Map<Class<?>, MethodHandle> VALUE_OF_MAP = new HashMap<>(1);
		private static final Map<Class<?>, Object> DFLTS = new HashMap<>();
		static {
			DFLTS.put(Boolean.class, Boolean.FALSE);
			DFLTS.put(boolean.class, false);
			DFLTS.put(Byte.class, Byte.valueOf("0"));
			DFLTS.put(byte.class, Byte.valueOf("0").byteValue());
			DFLTS.put(Number.class, 0L);
			DFLTS.put(Short.class, Short.valueOf("0"));
			DFLTS.put(short.class, Short.valueOf("0").shortValue());
			DFLTS.put(Character.class, Character.valueOf(' '));
			DFLTS.put(char.class, ' ');
			DFLTS.put(Integer.class, Integer.valueOf(0));
			DFLTS.put(int.class, 0);
			DFLTS.put(Long.class, Long.valueOf(0));
		    DFLTS.put(long.class, 0L);
		    DFLTS.put(Float.class, Float.valueOf(0F));
		    DFLTS.put(float.class, 0F);
		    DFLTS.put(Double.class, Double.valueOf(0D));
		    DFLTS.put(double.class, 0D);
		    DFLTS.put(BigInteger.class, BigInteger.valueOf(0L));
		    DFLTS.put(BigDecimal.class, BigDecimal.valueOf(0D));
		}
		private final String fieldName;
		private MethodHandle accessor;
		private MethodHandle setter;
		private final Class<F> declaredFieldType;
		private T target;

		/**
		 * Constructor
		 * 
		 * @param target
		 *            the {@linkplain #getTarget()} for the
		 *            {@linkplain MethodHandle}s
		 * @param fieldName
		 *            the field name defined in the {@linkplain #getTarget()}
		 * @param declaredFieldType
		 *            the declared field type for the
		 *            {@linkplain #getFieldName()}
		 */
		protected FieldHandle(final T target, final String fieldName, 
				final Class<F> declaredFieldType) {
			super();
			this.fieldName = fieldName;
			this.declaredFieldType = declaredFieldType;
			this.target = target;
			updateMethodHandles();
		}

		/**
		 * Updates the {@linkplain #getAccessor()} and {@linkplain #getSetter()}
		 * using the current {@linkplain #getTarget()} and
		 * {@linkplain #getFieldName()}. {@linkplain MethodHandle}s are
		 * immutable so new ones are created.
		 */
		protected void updateMethodHandles() {
			this.accessor = buildAccessorWithLikelyPrefixes(getTarget(), getFieldName());
			this.setter = buildSetter(getAccessor(), getTarget(), getFieldName());
		}
		
		/**
		 * Attempts to build a {@linkplain MethodHandle} accessor for the field
		 * name using common prefixes used for methods to access a field
		 * 
		 * @param target
		 *            the target object that the accessor is for
		 * @param fieldName
		 *            the field name that the accessor is for
		 * @return the accessor {@linkplain MethodHandle}
		 * @throws NoSuchMethodException
		 *             thrown when an accessor cannot be found for the field
		 */
		protected static MethodHandle buildAccessorWithLikelyPrefixes(final Object target, 
				final String fieldName) {
			final MethodHandle mh = buildAccessor(target, fieldName, "get", "is", "has");
			if (mh == null) {
				//throw new NoSuchMethodException(fieldName + " on " + target);
				throw new IllegalArgumentException(fieldName + " on " + target);
			}
			return mh;
		}

		/**
		 * Attempts to build a {@linkplain MethodHandle} accessor for the field
		 * name using common prefixes used for methods to access a field
		 * 
		 * @param target
		 *            the target object that the accessor is for
		 * @param fieldName
		 *            the field name that the accessor is for
		 * @return the accessor {@linkplain MethodHandle}
		 * @param fieldNamePrefix
		 *            the prefix of the method for the field name
		 * @return the accessor {@linkplain MethodHandle}
		 */
		protected static MethodHandle buildAccessor(final Object target, 
				final String fieldName, final String... fieldNamePrefix) {
			final String accessorName = buildMethodName(fieldNamePrefix[0], fieldName);
			try {
				return MethodHandles.lookup().findVirtual(target.getClass(), accessorName, 
						MethodType.methodType(
								target.getClass().getMethod(
										accessorName).getReturnType())).bindTo(target);
			} catch (final NoSuchMethodException e) {
				return fieldNamePrefix.length <= 1 ? null : 
					buildAccessor(target, fieldName, 
						Arrays.copyOfRange(fieldNamePrefix, 1, 
								fieldNamePrefix.length));
			} catch (final Throwable t) {
				throw new IllegalArgumentException(
						"Unable to resolve accessor " + accessorName, t);
			}
		}
		
		/**
		 * Builds a setter {@linkplain MethodHandle}
		 * 
		 * @param accessor
		 *            the field's accesssor that will be used as the parameter type
		 *            for the setter
		 * @param target
		 *            the target object that the setter is for
		 * @param fieldName
		 *            the field name that the setter is for
		 * @return the setter {@linkplain MethodHandle}
		 */
		protected static MethodHandle buildSetter(final MethodHandle accessor, 
				final Object target, final String fieldName) {
			try {
				final MethodHandle mh1 = MethodHandles.lookup().findVirtual(target.getClass(), 
						buildMethodName("set", fieldName), 
						MethodType.methodType(void.class, 
								accessor.type().returnType())).bindTo(target);
				return mh1;
			} catch (final Throwable t) {
				throw new IllegalArgumentException("Unable to resolve setter "
						+ fieldName, t);
			}
		}
		
		/**
		 * Puts a <code>valueOf</code> {@linkplain MethodHandle} value using the
		 * target class as a key
		 * 
		 * @param target
		 *            the target object that the <code>valueOf</code> is for
		 */
		protected static void putValueOf(final Class<?> target) {
			if (VALUE_OF_MAP.containsKey(target)) {
				return;
			}
			try {
				final MethodHandle mh1 = MethodHandles.lookup().findStatic(
						target, "valueOf",
						MethodType.methodType(target, String.class));
				VALUE_OF_MAP.put(target, mh1);
			} catch (final Throwable t) {
				// class doesn't support it- do nothing
			}
		}

		/**
		 * Attempts to invoke a <code>valueOf</code> using the
		 * {@linkplain #getDeclaredFieldType()} class
		 * 
		 * @param value
		 *            the value to invoke the <code>valueOf</code> method on
		 * @return the result (null if the operation fails)
		 */
		public F valueOf(final String value) {
			return valueOf(getDeclaredFieldType(), value);
		}
		
		/**
		 * Attempts to invoke a <code>valueOf</code> using the
		 * specified class
		 * 
		 * @param valueOfClass
		 *            the class to attempt to invoke a <code>valueOf</code>
		 *            method on
		 * @param value
		 *            the value to invoke the <code>valueOf</code> method on
		 * @return the result (null if the operation fails)
		 */
		@SuppressWarnings("unchecked")
		public static <VT> VT valueOf(final Class<VT> valueOfClass, 
				final Object value) {
			if (value != null && String.class.isAssignableFrom(valueOfClass)) {
				return (VT) value.toString();
			}
			if (!VALUE_OF_MAP.containsKey(valueOfClass)) {
				putValueOf(valueOfClass);
			}
			if (VALUE_OF_MAP.containsKey(valueOfClass)) {
				try {
					return (VT) VALUE_OF_MAP.get(valueOfClass).invoke(value);
				} catch (final Throwable t) {
					throw new IllegalArgumentException(String.format(
							"Unable to invoke valueOf on %1$s using %2$s", 
							value, valueOfClass), t);
				}
			}
			return null;
		}

		/**
		 * Gets a default value for the {@linkplain #getDeclaredFieldType()}
		 * 
		 * @return the default value
		 */
		public F defaultValue() {
			return defaultValue(getDeclaredFieldType());
		}

		/**
		 * Gets a default value for the specified class
		 * 
		 * @param clazz
		 *            the class
		 * @return the default value
		 */
		@SuppressWarnings("unchecked")
		public static <VT> VT defaultValue(final Class<VT> clazz) {
			return (VT) (DFLTS.containsKey(clazz) ? DFLTS.get(clazz) : null);
		}

		/**
		 * Builds a method name using a prefix and a field name
		 * 
		 * @param prefix
		 *            the method's prefix
		 * @param fieldName
		 *            the method's field name
		 * @return the method name
		 */
		protected static String buildMethodName(final String prefix, 
				final String fieldName) {
			return (fieldName.startsWith(prefix) ? fieldName : prefix + 
				fieldName.substring(0, 1).toUpperCase() + 
					fieldName.substring(1));
		}

		/**
		 * Sets the derived value from {@linkplain #deriveValueFromAccessor()}
		 * using {@linkplain #getSetter()}
		 * 
		 * @see #deriveValueFromAccessor()
		 * @return the accessor's return target value
		 */
		public F setDerivedValueFromAccessor() {
			F derived = null;
			try {
				derived = deriveValueFromAccessor();
				getSetter().invoke(derived);
			} catch (final Throwable t) {
				throw new RuntimeException(String.format(
						"Unable to set %1$s on %2$s", derived, 
						getTarget()), t);
			}
			return derived;
		}
		
		/**
		 * Gets an accessor's return target value obtained by calling the
		 * accessor's {@linkplain MethodHandle#invoke(Object...)} method. When
		 * the value returned is <code>null</code> an attempt will be made to
		 * instantiate it using either by using a default value from
		 * {@linkplain #DFLTS} (for primatives) or
		 * {@linkplain Class#newInstance()} on the accessor's
		 * {@linkplain MethodType#returnType()} method.
		 * 
		 * @return the accessor's return target value
		 */
		@SuppressWarnings("unchecked")
		protected F deriveValueFromAccessor() {
			F targetValue = null;
			try {
				targetValue = (F) getAccessor().invoke();
			} catch (final Throwable t) {
				targetValue = null;
			}
			if (targetValue == null) {
				try {
					if (DFLTS.containsKey(getFieldType())) {
						targetValue = (F) DFLTS.get(getFieldType());
					} else {
						targetValue = (F) getAccessor().type().returnType().newInstance();
					}
				} catch (final Exception e) {
					throw new IllegalArgumentException(
							String.format("Unable to get accessor return instance for %1$s using %2$s.", 
									getAccessor(), getAccessor().type().returnType()));
				}
			}
			return targetValue;
		}
		
		/**
		 * Binds a new target to the {@linkplain FieldHandle}
		 * 
		 * @param target
		 *            the target to bind to
		 */
		public void setTarget(final T target) {
			if (getTarget().equals(target)) {
				return;
			}
			this.target = target;
			updateMethodHandles();
		}
		
		public T getTarget() {
			return target;
		}

		public String getFieldName() {
			return fieldName;
		}

		/**
		 * @return the getter
		 */
		protected MethodHandle getAccessor() {
			return accessor;
		}

		/**
		 * @return the setter
		 */
		protected MethodHandle getSetter() {
			return setter;
		}

		/**
		 * @return the declared field type of the property value
		 */
		public Class<F> getDeclaredFieldType() {
			return declaredFieldType;
		}
		
		/**
		 * @return the field type from {@linkplain #getAccessor()} of the
		 *         property value
		 */
		public Class<?> getFieldType() {
			return getAccessor().type().returnType();
		}
	}
}

In A Bind

Although all the values of the POJO will always reflect what is set by bound JavaFX control properties, any changes made directly to the underlying POJO bean will not be reflected in the JavaFX UI until the next time the UI is refreshed. This is due to Java’s lack of native dependency injection instrumentation without a Java agent or byte-code manipulation. One possible, although not very efficient solution, would be to tap into some sort of JavaFX life-cycle call. I’d love to hear your ideas on how to accomplish this without any add-on libraries. So, feel free to leave me a comment :)

JavaFX Programmatic POJO Expression Bindings (Part I)

UPDATE: SEE PART II

 

The Ties That Bind

A common feature found in most UI frameworks is the ability to bind an application’s domain model to a UI control. JavaFX uses the JavaBeans component architecture as a basis to handle bindings. JavaFX bindings provide an abstraction layer that allows properties to be bidirectionally bound. When either one of the property’s value changes the other property will reflect the change. Like many presentation layer implementations, JavaFX provides a means in FXML to bind your own properties to a JavaFX control properties to ensure the property values are the same (similar in syntax to the Expression Language used in JSP).

The Problem

While the JavaBeans API/design pattern provides a clean separation from other business specific data it does however inherit some additional coding requirements in order to provide a relationship between it and an application’s POJOs. Say, for instance, we had a Person POJO that contained multiple fields for contact information.

public class Person {
	private String firstName;
	private String lastName;
	public String getFirstName() {
		return firstName;
	}
	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}
	public String getLastName() {
		return lastName;
	}
	public void setLastName(String lastName) {
		this.lastName = lastName;
	}
}

In order to use an instance of a Person and follow the design patterns set forth by JavaFX properties we would need to create another class that would translate each of the Person fields into JavaFX recognizable Properties.

public class PersonProperties {
	private final Person person;
	private StringProperty firstName = new SimpleStringProperty() {
		@Override
		public void set(String v) {
			super.set(v);
			person.setFirstName(v);
		}
	};
	private StringProperty lastName = new SimpleStringProperty() {
		@Override
		public void set(String v) {
			super.set(v);
			person.setLastName(v);
		}
	};
	public PersonProperties(Person person) {
		this.person = person;
	}
	public final String getFirstName() {
		return firstName.get();
	}
	public final void setFirstName(String value) {
		firstName.set(value);
	}
	public StringProperty firstNameProperty() {
		return firstName;
	}
	public final String getLastName() {
		return lastName.get();
	}
	public final void setLastName(String value) {
		lastName.set(value);
	}
	public StringProperty lastNameProperty() {
		return lastName;
	}
}

The Solution

As you can see from the prior Person example the amount of extra code that would need to be generated could easily become very large and difficult to maintain. As mentioned before FXML allows for expression bindings between different properties. For example, we could take a personProperties.firstName property and bind it to a JavaFX control…

<label></label>

If we take a similar approach that FXML uses for expression bindings and apply it in a different manner we could potentially abstract the Person properties into a generic pattern that would walk an expression. Unfortunately, the plumbing FXML uses to accomplish the expression evaluation isn’t completely exposed publicly. However, we can accomplish the same thing using Java 7′s new java.lang.invoke.MethodHandle (we could use the java.lang.reflect API, but this could possibly be much slower).

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Arrays;
import javafx.beans.property.ObjectPropertyBase;

public class PathProperty extends ObjectPropertyBase {
	private final String fieldPath;
	private PropertyMethodHandles propMethHandles;
	private final B bean;

	public PathProperty(final B bean, final String fieldPath, final Class type) {
		super();
		this.bean = bean;
		this.fieldPath = fieldPath;
		try {
			this.propMethHandles = PropertyMethodHandles.build(getBean(), getName());
		} catch (final Throwable t) {
			throw new RuntimeException(String.format(
					"Unable to instantiate expression %1$s on %2$s",
					getBean(), getName()), t);
		}
	}
	@Override
	public void set(T v) {
		try {
			getPropMethHandles().getSetter().invoke(v);
			super.set(v);
		} catch (final Throwable t) {
			throw new RuntimeException("Unable to set value: " + v, t);
		}
	};
	@Override
	public T get() {
		try {
			// TODO : here we are lazily loading the property which will prevent any property listeners
			// from receiving notice of a direct model field change until the next time the get method
			// is called on the PathProperty
			final T prop = (T) getPropMethHandles().getAccessor().invoke();
			if (!super.get().equals(prop)) {
				super.set(prop);
			}
			return super.get();
		} catch (final Throwable t) {
			throw new RuntimeException("Unable to get value", t);
		}
	}
	@Override
	public B getBean() {
		return bean;
	}
	@Override
	public String getName() {
		return fieldPath;
	}
	public PropertyMethodHandles getPropMethHandles() {
		return propMethHandles;
	}

	public static class PropertyMethodHandles {
		private final String fieldName;
		private final MethodHandle accessor;
		private final MethodHandle setter;
		private Object setterArgument;

		protected PropertyMethodHandles(final Object target, final String fieldName,
				final boolean insertSetterArgument) throws NoSuchMethodException {
			this.fieldName = fieldName;
			this.accessor = buildGetter(target, fieldName);
			this.setter = buildSetter(getAccessor(), target, fieldName, insertSetterArgument);
		}
		public static PropertyMethodHandles build(final Object initialTarget,
				final String expString) throws NoSuchMethodException, IllegalStateException {
			final String[] expStr = expString.split("\\.");
			Object target = initialTarget;
			PropertyMethodHandles pmh = null;
			for (int i = 0; i < expStr.length; i++) {
				pmh = new PropertyMethodHandles(target, expStr[i], i < (expStr.length - 1));
				target = pmh.getSetterArgument();
			}
			return pmh;
		}
		protected MethodHandle buildGetter(final Object target, final String fieldName)
						throws NoSuchMethodException {
			final MethodHandle mh = buildAccessor(target, fieldName, "get", "is", "has");
			if (mh == null) {
				throw new NoSuchMethodException(fieldName);
			}
			return mh;
		}
		protected MethodHandle buildSetter(final MethodHandle accessor,
				final Object target, final String fieldName,
				final boolean insertSetterArgument) {
			if (insertSetterArgument) {
				try {
					this.setterArgument = accessor.invoke();
				} catch (final Throwable t) {
					this.setterArgument = null;
				}
				if (getSetterArgument() == null) {
					try {
						this.setterArgument = accessor.type().returnType().newInstance();
					} catch (final Exception e) {
						throw new IllegalArgumentException(
								String.format("Unable to build setter expression for %1$s using %2$s.",
										fieldName, accessor.type().returnType()));
					}
				}
			}
			try {
				final MethodHandle mh1 = MethodHandles.lookup().findVirtual(target.getClass(),
						buildMethodName("set", fieldName),
						MethodType.methodType(void.class,
								accessor.type().returnType())).bindTo(target);
				if (getSetterArgument() != null) {
					mh1.invoke(getSetterArgument());
				}
				return mh1;
			} catch (final Throwable t) {
				throw new IllegalArgumentException("Unable to resolve setter "
						+ fieldName, t);
			}
		}
		protected static MethodHandle buildAccessor(final Object target,
				final String fieldName, final String... fieldNamePrefix) {
			final String accessorName = buildMethodName(fieldNamePrefix[0], fieldName);
			try {
				return MethodHandles.lookup().findVirtual(target.getClass(), accessorName,
						MethodType.methodType(
								target.getClass().getMethod(
										accessorName).getReturnType())).bindTo(target);
			} catch (final NoSuchMethodException e) {
				return fieldNamePrefix.length 					buildAccessor(target, fieldName,
						Arrays.copyOfRange(fieldNamePrefix, 1,
								fieldNamePrefix.length));
			} catch (final Throwable t) {
				throw new IllegalArgumentException(
						"Unable to resolve accessor " + accessorName, t);
			}
		}
		public static String buildMethodName(final String prefix,
				final String fieldName) {
			return (fieldName.startsWith(prefix) ? fieldName : prefix +
				fieldName.substring(0, 1).toUpperCase() +
					fieldName.substring(1));
		}
		public String getFieldName() {
			return fieldName;
		}
		public MethodHandle getAccessor() {
			return accessor;
		}
		public MethodHandle getSetter() {
			return setter;
		}
		public Object getSetterArgument() {
			return setterArgument;
		}
	}
}

Now with the above PathProperty we create bindings on the fly by passing a path expression string into it’s constructor. The path will be traversed until the last field is reached. It supports the instantiation of intermediate objects found while walking the path expression (as long as the class type of that object contains a no-argument constructor). So, revisiting the Person class, if we add another field called address that takes an Address class with an accessible getStreetName method we can do the following:

Person person = new Person();
PathProperty prop = new PathProperty(
				person, "address.streetName", String.class);
// bind it to a JavaFX control
Bindings.bindBidirectional(prop, myTextField.textProperty());

// apply value to the JavaFX property and verify the same value exists in the model
prop.set("123 1st Street");
System.out.println("Address Property: " + prop.get());
System.out.println("Address POJO: " + person.getAddress().getStreetName());
// apply value to the POJO field and verify the same value exists in the JavaFX property
person.getAddress().setStreetName("456 2nd Street");
System.out.println("Address Property: " + prop.get());
System.out.println("Address POJO: " + person.getAddress().getStreetName());

In A Bind

As you can see the values are virtually bound bidirectionally between the JavaFX property and the POJO. Any calls to PathProperty#set(T v) will dispatch the required events that allow JavaFX to reflect those changes in the bound property of the control. However, there are some drawbacks to this approach. You may have noticed the TODO in the PathProperty#get() method shown above. Due to Java’s lack of native dependency injection instrumentation without a Java agent or byte-code manipulation any listeners that are listening to a PathProperty will not be notified of changes made directly to the POJO until the next time the PathProperty#get() method is called. Lazy loading of property values is really less than adequate for most applications where domain POJOs are being altered outside the scope of JavaFX.

JavaFX Dialog Service

One of the nice features added as of JavaFX 2.x is the ability to refactor GUI related background tasks into Services using worker Tasks. This approach allows background tasks to run in a thread safe manner that doesn’t lock up your application while they are being run.

One of the most common application requirements is the need for a dialog window. JavaFX has nicely equipped us with a few options such as a PopupWindow or even an entirely different Stage. The Stage option provides for a more granular approach with more control over it’s behavior. So, it only seems natural that the marriage of a Stage and a Service can provide a reusable template for interactive dialog poups while avoiding unnecessary boilerplate code.

We’ll start by creating a simple example dialog box for logging into an application.

Validation using a Service

When the user clicks on the login button we would like to validate that the username and password fields are filled out. We could of course add a much more robust validation mechanism, but for demonstration purposes we just want to validate that they have not left the fields blank. To do this we have added a simple message header text to display exception messages via our dialog service (like the one below)

How it works

The first thing we need to do is create a Service that will create Tasks for showing and hiding our login window. When creating a task to run the Service determines if the supplied Stage is showing or not. If it’s showing than it knows to create a hide Task that will be responsible for hiding the dialog Stage when ran. When the Stage hasn’t been shown yet we create a show Task that will show the dialog Stage when ran. We also added a ChangeListener to the show Task that will auto-hide the dialog Stage when the show Task fails or is cancelled.

		@Override
		protected Task<Void> createTask() {
			return window.isShowing() ? createHideTask() : createShowTask();
		}

		protected Task<Void> createShowTask() {
			final Task<Void> showTask = new Task<Void>() {
				@Override
				protected Void call() throws Exception {
					Platform.runLater(new Runnable() {
						public void run() {
							window.show();
							window.centerOnScreen();
						}
					});
					return null;
				}
			};
			showTask.stateProperty().addListener(new ChangeListener<State>() {
				@Override
				public void changed(final ObservableValue<? extends State> observable, 
						final State oldValue, final State newValue) {
					if (newValue == State.FAILED || newValue == State.CANCELLED) {
						Platform.runLater(createHideTask());
					}
				}
			});
			return showTask;
		}

		protected Task<Void> createHideTask() {
			final Task<Void> closeTask = new Task<Void>() {
				@Override
				protected Void call() throws Exception {
					window.hide();
					window.getScene().getRoot().setDisable(false);
					return null;
				}
			};
			return closeTask;
		}

Now that we have created the Tasks to show and hide the dialog Stage we need a way to run the dialog Service. What better way to do this than to pass in another Service into our dialog Service. That way we can listen for a SUCEEDED state from the passed “submit” service. When it completes successfully we know that it’s time to hide our dialog Stage. If an exception is thrown in the provided submit Service we know something went wrong and should show the exception message and prevent our dialog window from being hid. To do this we also need to pass in a Text node that we can update with the exception message when one is thrown by the passed submit service.

		protected DialogService(final Stage parent, final Stage window, 
				final Text messageHeader, final Service<Void> submitService) {
			this.window = window;
			this.parent = parent;
			this.submitService = submitService;
			this.submitService.stateProperty().addListener(new ChangeListener<State>() {
				@Override
				public void changed(final ObservableValue<? extends State> observable, 
						final State oldValue, final State newValue) {
					if (submitService.getException() != null) {
						// service indicated that an error occurred
						messageHeader.setText(submitService.getException().getMessage());
					} else if (newValue == State.SUCCEEDED) {
						// run a hide task... we are done!
						Platform.runLater(createHideTask());
					}
				}
			});
		}

The only thing remaining is creating a method for adding visual components to our dialog service. There are numerous ways to do this, but to keep it simple we created a static utility method that will construct our stage and dialog Service. It allows us to pass in an arbitrary number of child Nodes that will be added to a VBox that appears in-between the header Text and our Button FlowPane at the bottom. If any of the specified Nodes are a Button they will be added to the same FlowPane that the “submit” button that starts/restarts our dialog Service. The important thing is to ensure that the submit button starts/restarts the dialog Service when clicked. That way we can rerun the dialog service as many times as our application needs to. For example, if the supplied submit Service throws an exception to indicate the form entries are not valid we don’t want the dialog Service to successfully complete and close our dialog Stage, but rather wait for the user to correct the problem and proceed by clicking the login/submit button (at which point the supplied submit Service will be reran).

	public static DialogService dialog(final Stage parent, final String title, 
			final String headerText, final Image icon, final String submitLabel, 
			final double width, final double height, final Service<Void> submitService, 
			final Node... children) {
		final Stage window = new Stage();
		final Text header = TextBuilder.create().text(headerText).styleClass(
				"dialog-title").wrappingWidth(width / 1.2d).build();
		final Text messageHeader = TextBuilder.create().styleClass("dialog-message"
				).wrappingWidth(width / 1.2d).build();
		final DialogService service = new DialogService(parent, window, 
				messageHeader, submitService);
		window.initModality(Modality.APPLICATION_MODAL);
		window.initStyle(StageStyle.TRANSPARENT);
		if (icon != null) {
			window.getIcons().add(icon);
		}
		if (title != null) {
			window.setTitle(title);
		}
		final VBox content = VBoxBuilder.create().styleClass("dialog").build();
		content.setMaxSize(width, height);
		window.setScene(new Scene(content, width, height, Color.TRANSPARENT));
		if (parent != null) {
			window.getScene().getStylesheets().setAll(parent.getScene().getStylesheets());
		}
		final Button submitBtn = ButtonBuilder.create().text(submitLabel).defaultButton(
				true).onAction(new EventHandler<ActionEvent>() {
			@Override
			public void handle(final ActionEvent actionEvent) {
				submitService.restart();
			}
		}).build();
		final FlowPane flowPane = new FlowPane();
		flowPane.setAlignment(Pos.CENTER);
		flowPane.setVgap(20d);
		flowPane.setHgap(10d);
		flowPane.setPrefWrapLength(width);
		flowPane.getChildren().add(submitBtn);
		content.getChildren().addAll(header, messageHeader);
		if (children != null && children.length > 0) {
			for (final Node node : children) {
				if (node == null) {
					continue;
				}
				if (node instanceof Button) {
					flowPane.getChildren().add(node);
				} else {
					content.getChildren().add(node);
				}
			}
		}
		content.getChildren().addAll(flowPane);
		return service;
	}

CSS Mojo

Finally, we can provide a little CSS mojo to our login form. We’ll call it dialog.css and place it in the same path as our final Application example below.

/*******************************************************************************
*                                                                             *
* Dialog CSS                                                                  *
*                                                                             *
******************************************************************************/
.dialog-title {
    -fx-fill: white;
    -fx-font-style: oblique;
    -fx-font-size: 14px;
    -fx-effect: dropshadow( one-pass-box , rgba(75,75,75,0.8) , 0, 0.0 , 2, 2);
}
.dialog-message {
    -fx-fill: crimson;
    -fx-font-style: oblique;
    -fx-font-size: 12px;
    -fx-effect: dropshadow( one-pass-box , rgba(0,0,0,0.8) , 0, 0.0 , 1, 1);
}
.dialog {
    -fx-base: #505359;
    -fx-background: #505359;
    -fx-spacing: 10;
    -fx-padding: 10 10 10 10;
    -fx-background-color:
        linear-gradient(#686868 0%, #232723 25%, #373837 75%, #757575 100%),
        linear-gradient(#020b02, #3a3a3a),
        linear-gradient(#9d9e9d 0%, #6b6a6b 20%, #343534 80%, #242424 100%),
        linear-gradient(#8a8a8a 0%, #6b6a6b 20%, #343534 80%, #262626 100%),
        linear-gradient(#777777 0%, #606060 50%, #505250 51%, #2a2b2a 100%);
      -fx-alignment: center;
      -fx-border-width: 3;
    -fx-border-color: 
      linear-gradient(#686868 0%, #232723 25%, #373837 75%, #757575 100%) 
            linear-gradient(#686868 10%, white 40%, #343534 90%, #757575 100%)
            linear-gradient(#686868 0%, #232723 25%, #373837 75%, #757575 100%)
            linear-gradient(#686868 10%, white 40%, #343534 90%, #757575 100%);
      -fx-border-insets: 1 1 1 1;
      -fx-border-radius: 8 8 8 8;
      -fx-background-radius: 10 10 10 10;
}
/*******************************************************************************
 *                                                                             *
 * Button                                                                      *
 *                                                                             *
 ******************************************************************************/
.button {
    -fx-background-color:
        #a6b5c9,
        linear-gradient(#303842 0%, #3e5577 20%, #375074 100%),
        linear-gradient(#768aa5 0%, #849cbb 5%, #5877a2 50%, #486a9a 51%, #4a6c9b 100%);
    -fx-background-insets: 0 0 -1 0,0,1;
    -fx-background-radius: 0;
    -fx-padding: 7 30 7 30;
    -fx-font-size: 12px;
    -fx-text-fill: white;
}
.button Text {
    -fx-effect: dropshadow( one-pass-box , rgba(0,0,0,0.8) , 0, 0.0 , 0 , -1 );
}
.button:focused {
    -fx-color: -fx-focused-base;
    -fx-effect: dropshadow( one-pass-box , black, 0, 0.0 , 2, 2);
    -fx-background-insets: -1.4, 0, 1, 2;
}
.button:hover {
    -fx-color: -fx-focused-base;
    -fx-background-color:
        #b6c5d9,
        linear-gradient(#404852 0%, #4e6587 20%, #476084 100%),
        linear-gradient(#869ab5 0%, #94accb 5%, #6887b2 50%, #587aaa 51%, #5a7cab 100%);
    -fx-effect: dropshadow( one-pass-box , rgba(0,0,0,0.8) , 0, 0.0 , 2, 2);
    -fx-background-insets: -1.4, 0, 1, 2;
    -fx-background-radius:  6.4, 5, 4, 3;
}
.button:armed {
    -fx-color: -fx-pressed-base;
}
.button:default {
    -fx-base: -fx-accent;
}
.button:cancel {
     -fx-base: -fx-accent;
 }
.button:disabled {
    -fx-opacity: -fx-disabled-opacity;
}
.button:show-mnemonics .mnemonic-underline {
    -fx-stroke: -fx-text-fill;
}

Putting it all together

Below is an example Application that demonstrates the use of a dialog Service. The primary Stage of the application consists of a simple window with a Button that will call our dialog method that will launch a new dialog window. You may also notice that we also added some additional features such as a “lightbox” modality that will dim the primary Stage while the dialog window is shown (hard to see in the example because the background is black).

import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBuilder;
import javafx.scene.control.PasswordField;
import javafx.scene.control.PasswordFieldBuilder;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFieldBuilder;
import javafx.scene.effect.ColorAdjustBuilder;
import javafx.scene.effect.Effect;
import javafx.scene.image.Image;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.VBox;
import javafx.scene.layout.VBoxBuilder;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.scene.text.TextBuilder;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;

/**
 * {@linkplain DialogService} Demo
 */
public class DialogServiceTest extends Application {

	private DialogService dialogService;

	/**
	 * Main {@linkplain Application} entry point
	 *
	 * @param args
	 *            passed arguments
	 */
	public static void main(final String[] args) {
		try {
			Application.launch(DialogServiceTest.class, args);
		} catch (final Throwable t) {
			t.printStackTrace();
		}
	}

	/**
	 * Shows an example usage of a {@linkplain DialogService} that displays a
	 * login screen
	 *
	 * @param primaryStage
	 *            the primary application {@linkplain Stage}
	 * @throws Exception
	 *             when something goes wrong
	 */
	@Override
	public void start(final Stage primaryStage) throws Exception {
		// setup the primary stage with a simple button that will open
		final VBox rootNode = new VBox();
		rootNode.setAlignment(Pos.CENTER);
		final Button btn = new Button("Launch Login Dialog Window");
		btn.setOnMouseClicked(new EventHandler<MouseEvent>() {
			@Override
			public void handle(final MouseEvent event) {
				if (dialogService != null) {
					dialogService.hide();
				}
				dialogService = createLoginDialog(primaryStage);
		        dialogService.start();
			}
		});
		rootNode.getChildren().add(btn);
		primaryStage.setTitle("Dialog Service Demo");
		primaryStage.setScene(new Scene(rootNode, 800, 500, Color.BLACK));
		primaryStage.getScene().getStylesheets().add(
				DialogServiceTest.class.getResource("dialog.css").toExternalForm());
		primaryStage.show();
	}

	/**
	 * Creates a {@linkplain DialogService} that displays a
	 * login screen
	 *
	 * @param primaryStage
	 *            the primary application {@linkplain Stage}
	 */
	public DialogService createLoginDialog(final Stage primaryStage) {
        final TextField username = TextFieldBuilder.create().promptText(
				"Username").build();
		final PasswordField password = PasswordFieldBuilder.create().promptText(
				"Password").build();
		final Button closeBtn = ButtonBuilder.create().text("Close").build();
		final Service<Void> submitService = new Service<Void>() {
			@Override
			protected Task<Void> createTask() {
				return new Task<Void>() {
					@Override
					protected Void call() throws Exception {
						final boolean hasUsername = !username.getText()
								.isEmpty();
						final boolean hasPassword = !password.getText()
								.isEmpty();
						if (hasUsername && hasPassword) {
							// TODO : perform some sort of authentication here
							// or you can throw an exception to see the error
							// message in the dialog window
						} else {
							final String invalidFields = (!hasUsername ? username
									.getPromptText() : "")
									+ ' '
									+ (!hasPassword ? password.getPromptText()
											: "");
							throw new RuntimeException("Invalid "
									+ invalidFields);
						}
					}
				};
			}
		};
		final DialogService dialogService = dialog(primaryStage,
				"Test Dialog Window",
				"Please provide a username and password to access the application",
				null, "Login", 550d, 300d, submitService, closeBtn, username, password);
		if (closeBtn != null) {
		      closeBtn.setOnMouseClicked(new EventHandler<MouseEvent>() {
		            @Override
		            public void handle(final MouseEvent event) {
		                  dialogService.hide();
		            }
		      });
		}
		return dialogService;
	}

	/**
	 * Creates a dialog window {@linkplain Stage} that is shown when the
	 * {@linkplain DialogService#start()} is called and hidden when the submit
	 * {@linkplain Service#restart()} returns {@linkplain State#SUCCEEDED}. When
	 * a {@linkplain Task} throws an {@linkplain Exception} the
	 * {@linkplain Exception#getMessage()} will be used to update the
	 * messageHeader of the dialog.
	 *
	 * @param parent
	 *            the parent {@linkplain Stage}
	 * @param title
	 *            the text for the {@linkplain Stage#setTitle(String)}
	 * @param headerText
	 *            the text for the {@linkplain Text#setText(String)} header
	 * @param icon
	 *            the icon of the {@linkplain Stage}
	 * @param submitLabel
	 *            the text for the submit {@linkplain Button#setText(String)}
	 * @param width
	 *            the width of the {@linkplain Stage}
	 * @param height
	 *            the height of the {@linkplain Stage}
	 * @param submitService
	 *            the {@linkplain Service} ran whenever the submit
	 *            {@linkplain Button} is clicked
	 * @param children
	 *            the child {@linkplain Node}s that will be added between the
	 *            messageHeader and submit {@linkplain Button} (if any). If any
	 *            of the {@linkplain Node}s are {@linkplain Button}s they will
	 *            be added to the internal {@linkplain Button}
	 *            {@linkplain FlowPane} added to the bottom of the dialog.
	 * @return the {@linkplain DialogService}
	 */
	public static DialogService dialog(final Stage parent, final String title,
			final String headerText, final Image icon, final String submitLabel,
			final double width, final double height, final Service<Void> submitService,
			final Node... children) {
		final Stage window = new Stage();
		final Text header = TextBuilder.create().text(headerText).styleClass(
				"dialog-title").wrappingWidth(width / 1.2d).build();
		final Text messageHeader = TextBuilder.create().styleClass("dialog-message"
				).wrappingWidth(width / 1.2d).build();
		final DialogService service = new DialogService(parent, window,
				messageHeader, submitService);
		window.initModality(Modality.APPLICATION_MODAL);
		window.initStyle(StageStyle.TRANSPARENT);
		if (icon != null) {
			window.getIcons().add(icon);
		}
		if (title != null) {
			window.setTitle(title);
		}
		final VBox content = VBoxBuilder.create().styleClass("dialog").build();
		content.setMaxSize(width, height);
		window.setScene(new Scene(content, width, height, Color.TRANSPARENT));
		if (parent != null) {
			window.getScene().getStylesheets().setAll(parent.getScene().getStylesheets());
		}
		final Button submitBtn = ButtonBuilder.create().text(submitLabel).defaultButton(
				true).onAction(new EventHandler<ActionEvent>() {
			@Override
			public void handle(final ActionEvent actionEvent) {
				submitService.restart();
			}
		}).build();
		final FlowPane flowPane = new FlowPane();
		flowPane.setAlignment(Pos.CENTER);
		flowPane.setVgap(20d);
		flowPane.setHgap(10d);
		flowPane.setPrefWrapLength(width);
		flowPane.getChildren().add(submitBtn);
		content.getChildren().addAll(header, messageHeader);
		if (children != null && children.length > 0) {
			for (final Node node : children) {
				if (node == null) {
					continue;
				}
				if (node instanceof Button) {
					flowPane.getChildren().add(node);
				} else {
					content.getChildren().add(node);
				}
			}
		}
		content.getChildren().addAll(flowPane);
		return service;
	}

	/**
	 * A {@linkplain Service} for showing and hiding a {@linkplain Stage}
	 */
	public static class DialogService extends Service<Void> {

		private final Stage window;
		private final Stage parent;
		private final Effect origEffect;
		private final Service<Void> submitService;

		/**
		 * Creates a dialog service for showing and hiding a {@linkplain Stage}
		 *
		 * @param parent
		 *            the parent {@linkplain Stage}
		 * @param window
		 *            the window {@linkplain Stage} that will be shown/hidden
		 * @param messageHeader
		 *            the messageHeader {@linkplain Text} used for the service
		 *            that will be updated with exception information as the
		 *            submitService informs the {@linkplain DialogService} of
		 * @param submitService
		 *            the {@linkplain Service} that will be listened to for
		 *            {@linkplain State#SUCCEEDED} at which point the
		 *            {@linkplain DialogService} window {@linkplain Stage} will
		 *            be hidden
		 */
		protected DialogService(final Stage parent, final Stage window,
				final Text messageHeader, final Service<Void> submitService) {
			this.window = window;
			this.parent = parent;
			this.origEffect = hasParentSceneRoot() ? this.parent.getScene(
					).getRoot().getEffect() : null;
			this.submitService = submitService;
			this.submitService.stateProperty().addListener(new ChangeListener<State>() {
				@Override
				public void changed(final ObservableValue<? extends State> observable,
						final State oldValue, final State newValue) {
					if (submitService.getException() != null) {
						// service indicated that an error occurred
						messageHeader.setText(submitService.getException().getMessage());
					} else if (newValue == State.SUCCEEDED) {
						window.getScene().getRoot().setEffect(
								ColorAdjustBuilder.create().brightness(-0.5d).build());
						Platform.runLater(createHideTask());
					}
				}
			});
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		protected Task<Void> createTask() {
			return window.isShowing() ? createHideTask() : createShowTask();
		}

		/**
		 * @return a task that will show the service {@linkplain Stage}
		 */
		protected Task<Void> createShowTask() {
			final Task<Void> showTask = new Task<Void>() {
				@Override
				protected Void call() throws Exception {
					Platform.runLater(new Runnable() {
						public void run() {
							if (hasParentSceneRoot()) {
								parent.getScene().getRoot().setEffect(
										ColorAdjustBuilder.create().brightness(-0.5d).build());
							}
							window.show();
							window.centerOnScreen();
						}
					});
					return null;
				}
			};
			showTask.stateProperty().addListener(new ChangeListener<State>() {
				@Override
				public void changed(final ObservableValue<? extends State> observable,
						final State oldValue, final State newValue) {
					if (newValue == State.FAILED || newValue == State.CANCELLED) {
						Platform.runLater(createHideTask());
					}
				}
			});
			return showTask;
		}

		/**
		 * @return a task that will hide the service {@linkplain Stage}
		 */
		protected Task<Void> createHideTask() {
			final Task<Void> closeTask = new Task<Void>() {
				@Override
				protected Void call() throws Exception {
					window.hide();
					if (hasParentSceneRoot()) {
						parent.getScene().getRoot().setEffect(origEffect);
					}
					window.getScene().getRoot().setDisable(false);
					return null;
				}
			};
			return closeTask;
		}

		/**
		 * @return true when the parent {@linkplain Stage#getScene()} has a
		 *         valid {@linkplain Scene#getRoot()}
		 */
		private boolean hasParentSceneRoot() {
			return this.parent != null && this.parent.getScene() != null
					&& this.parent.getScene().getRoot() != null;
		}

		/**
		 * Hides the dialog used in the {@linkplain Service}
		 */
		public void hide() {
			Platform.runLater(createHideTask());
		}
	}
}

Conclusion

As you can see there a numerious applications for JavaFX Service abstractions that can be used to reduce boilerplate code. Another possible useage could be a progress dialog Service that will automatically close when a ProgressIndicator has completed. That doesn’t render anything that’s much different than a normal progress bar, but what if we need some intermittent user interaction before it completes (like a wizard)? That is where using a Service would come in handy!

Follow

Get every new post delivered to your Inbox.