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:
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.
Assuming at some level there is at least one
Atom
or other Clojure mutable type (ref/var/agent/etc) (whether it be a singleAtom
containing the entire in-memory design database, or whether the design database is an immutable list of mutableAtoms
), which of the [MVP, MCP, MVVM, etc.] models is best for this type of creation?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 exampleObservableMap
andObservableMapValue
, and then dozens upon dozens of implementing classes such asIntegerProperty
andSimpleIntegerProperty
... geez! wtf?. Assuming that I have to create some Clojure objects (defrecord
, etc.) and implement some of theObservable
interface methods on my mostly immutable objects, can I just stick withObservable
, or must I implement each one down to the leaf node, ieObservableIntegerValue
, 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