JavaFX and Clojure: binding observables to immutable objects
Asked Answered
D

1

23

I've been trying to figure out the approach to take to allow a JavaFX TableView (or any other JavaFX thing) to represent some Clojure data, and allow the user to manipulate the data through the GUI.

For this discussion, let's assume I have a list/vector of maps, ie something like [{:col1 "happy1" :col2 "sad1"} {:col1 "happy2" :col2 "sad2"}] and I want it to display in a graphical table as follows:

mykey1      mykey2
------------------
happy1      sad1
happy2      sad2

Pretty straightforward. This has been done a gazillion times in the history of the world.

The problem is the TableView insists on taking an ObservableList, etc., which is inherently a mutable thing, as are all the Observables in JavaFX. This is great for keeping the table up to date, and in a mutable model, it's also great for allowing the user to directly manipulate the data via the GUI. I'm not an expert, but in this case it seems JavaFX wants the GUI object to actually contain the real data. This seems funny (not ha ha) to me. To maintain my own model and communicate between GUI and model via some API or interface also implies that I'm maintaining the data in two places: in my own model, and in the GUI. Is this the Correct Way of Doing Things? Maybe this is ok since the GUI only ever displays a small fraction of the total data, and it lets my model data just be normal model data, not an instance of some Java-derived type.

So this leads to the following three general questions when trying to put a GUI on a stateless/immutable model:

  1. How can the model underneath be truly immutable if the GUI necessarily allows you to change things? I'm thinking specifically of some sort of design tool, editor, etc., where the user is explicitly changing things around. For example LightTable is an editor, yet the story is it is based on immutable data. How can this be? I'm not interested in FRP for this discussion.

  2. Assuming at some level there is at least one Atom or other Clojure mutable type (ref/var/agent/etc) (whether it be a single Atom containing the entire in-memory design database, or whether the design database is an immutable list of mutable Atoms), which of the [MVP, MCP, MVVM, etc.] models is best for this type of creation?

  3. JavaFX has littered the class hierarchy with every imaginable variation of Observable interface (http://docs.oracle.com/javafx/2/api/javafx/beans/Observable.html), with such gems as Observable[whatever]Value, including for example ObservableMap and ObservableMapValue, and then dozens upon dozens of implementing classes such as IntegerProperty and SimpleIntegerProperty... geez! wtf?. Assuming that I have to create some Clojure objects (defrecord, etc.) and implement some of the Observable interface methods on my mostly immutable objects, can I just stick with Observable, or must I implement each one down to the leaf node, ie ObservableIntegerValue, etc.?

What is the correct high level approach? Maintain a single top-level atom which is replaced every time the user changes a value? Maintain a thousand low-level atoms? Let my values live as JavaFX Observables, and forget about Clojure data structs? Implement my own set of Observables in Clojure using some reify/proxy/gen-class, but implement them as immutables that get replaced each time a change is made? Is there a need or place for Clojure's add-watch function? I'd very much prefer that my data just be normal data in Clojure, not a "type" or an implementation of an interface of anything. An Integer should be an Integer, etc.

thanks

Dryclean answered 31/7, 2014 at 22:8 Comment(3)
Maybe this helps: github.com/halgari/fn-fx, video introduction here: youtube.com/watch?v=hJ8GZxhsaVQWoolpack
i think that cursors approach from ClojureScript land is also related: github.com/reagent-project/reagent-cursor#cursorsDegreeday
I don't have anything to add that you're not already thinking about, but: Some Java libs, or some methods in some Java libs, are easy to use in Clojure, and some aren't. Some designs that are perfectly reasonable in Java are contrary to the intentions of Clojure's designers. Using libraries with those designs won't be pretty. You can do it (I have), but it won't be Clojurely. Still might be a better option than writing a new library from scratch in Clojure, obviously.Palstave
D
2

I would add a watch to the atom, and then update your observable based on the diff. http://clojuredocs.org/clojure.core/add-watch

Donnydonnybrook answered 8/2, 2016 at 11:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.