Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make behavior more similar to cljs freactive: Reactively bind children nodes #6

Open
alexandergunnarson opened this issue Jul 15, 2015 · 5 comments

Comments

@alexandergunnarson
Copy link

I was trying to do the following just as an experiment within a function:

(defn create-view []
  (fx/compile-fx
    [:v-box
      (rx (if (= @view :web)
          [:web-view]
          [:button {:text "Click me!"}])])

(fx/sandbox #'create-view)

The exception I receive is:

Exception in thread "JavaFX Application Thread" java.lang.ClassCastException: freactive.ReactiveExpression cannot be cast to javafx.scene.Node

Besides the differences in properties and tag names, something like this should work in the ClojureScript equivalent (e.g., with divs and buttons and the like). What needs to be done to make it work as in ClojureScript, i.e., by reactively binding children nodes to parent nodes as opposed to just properties to nodes?

@aaronc
Copy link
Owner

aaronc commented Jul 15, 2015

So fx-clj does not support child node binding yet, but it could be done. I think it would be easier to do now that the cljs freactive is more structured. I won't have time to work on this in the foreseeable future, but a PR would be accepted. If it is something that you'd want to tackle, I can explain how to approach it.

@alexandergunnarson
Copy link
Author

I think, having given it a bit more time, I would like to do a PR for this. Any explanations or insights you have would very much be appreciated.

Another thing I'd like to do is to have a TableView (and a ListView, I suppose) that accepts reactive atoms/cursors as ItemsPropertys instead of only accepting ObservableLists. That probably requires some Java code referencing Clojure code, because reify can only implement interfaces, and proxy will probably be too slow. I'd rather do that than create a class that extends ObservableList, I think.

One random question: will a view need to be re-created each time it is reactively invalidated? E.g.:

(fx/compile-fx
    [:v-box
      (rx (if (= @view :web)
          [:web-view]
          [:button {:text "Click me!"}])])

Will a new WebView be created every time the reactive atom view is invalidated? That seems expensive. Perhaps it's just an issue with my naive thought process and that the following does just what I'm wanting to do:

(let [web-view (fx/compile-fx [:web-view])]
  (fx/compile-fx
      [:v-box
        (rx (if (= @view :web)
            web-view
            [:button {:text "Click me!"}])]))

@alexandergunnarson
Copy link
Author

This is what I've currently hacked together to get some semblance of immutability with an observable collection. Not the prettiest, but here it is:

(defrecord FXObservableAtom
  [^clojure.lang.IAtom immutable
   ^com.sun.javafx.collections.ObservableListWrapper observable])

(defn fx-observable-atom [v]
  (FXObservableAtom. (atom v) (->observable v)))

(defnt oswap!*
  [List]
    ([x f args]
      (condp = f
        conj    (fx/run! (.addAll x args))
        assoc   (fx/run!
                  (doseq [i v (into {} args)]
                    (.set x i v)))
        update  (let [i        (first args)
                      update-f (second args)]
                  (fx/run! (.set x i (update-f (.get x i)))))
        (throw+ (Err. :illegal-operation "Not an allowed operation for oswap:" f))))
  [FXObservableAtom]
    ([x f args]
      (oswap!* (:observable x) f args)
      (apply swap! (:immutable x) f args)))

(defn oswap! [x f & args]
  (oswap!* x f args))

Note that defnt is short for defn-typed. It's a macro I created around defprotocol and extend-protocol. Also note that oswap is short for observable swap.

@aaronc
Copy link
Owner

aaronc commented Jul 21, 2015

Cool. So first of all have you taken a look at how fx-clj sets the default property on nodes: https://github.com/aaronc/fx-clj/blob/master/src/fx_clj/core/pset.clj? Also, make sure you understand what JavaFX is doing with this attribute: https://docs.oracle.com/javase/8/javafx/api/javafx/beans/DefaultProperty.html.

My recommended approach to dealing with collection stuff is the IProjection API. This type wraps a cursor to a set of items (of course it's cljs only for now) - https://github.com/aaronc/freactive.core/blob/master/src/clojure/freactive/core.cljc#L984-L1054 - and "projects" it onto the DOM via https://github.com/aaronc/freactive/blob/develop/src/freactive/ui_common.cljs#L204-L257. IMHO, this approach allows for a lot of flexibility in the transformation from lists of items to the UI. Other approaches are definitely possible - what you're doing could work I'm pretty sure. I think it's really a matter of figuring out what is the most suitable approach for your application's needs. FYI, some parts of the IProjection code may be renamed (for clarity), but I don't expect major changes in functionality at this point - there are also a few unimplemented bits. Anyway, if you get a chance to check that code out let me know if you have questions.

One thing to keep in mind is that in freactive for the DOM, there are "virtual elements" whereas in fx-clj, I am just using Node's directly. I think it would be a major rewrite to do it differently. Using "virtual elements" may provide a few benefits, but I don't think it's really worth it for the work involved. Probably just "projecting" a cursor to some items onto nodes will do a lot.

Does that help?

@alexandergunnarson
Copy link
Author

I took a look at pset around a week ago and from what I remember, it gets the default property via reflection initially but it memoizes the results to avoid future reflective calls. My memory about that isn't too great, though. As for the IProjection API, I haven't taken a look at that yet, but I certainly will. What you said definitely helps, and I'll be sure to ask when I have further questions after I've started implementing some of the ClojureScript functionality in Clojure. My guess is that it could be a slow-ish process simply because I'm very busy at the moment, but I will make it a priority to work on it because making hacky workarounds like the one I made up above isn't my preferred approach.

Thanks for your help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants