Multi-pass rendering using C2? (or: help me improve my solution)

71 views
Skip to first unread message

Romain

unread,
Oct 5, 2012, 5:11:39 AM10/5/12
to c2-...@googlegroups.com
Hi C2 users,

I have been toying with a C2 UI for which I use a two-pass rendering method.

My use case is a dataflow diagram in which device are connected to other devices via output and input slots. A picture will make this clearer:

Dataflow diagram
















The devices ("boxes") are divs, input and output slots are nested divs, and the connections are rendered on a background SVG element.

Now, I cannot draw the connections until the slots positions are known (i.e. the elements have been added to the DOM), hence my current workaround, bind-then!, that takes a callback function:

(defmacro bind-then!
  "Similar to C2's bind!, but takes a callback function that
  gets triggered after rendering"
    [el hiccup-el cf]
  `(let [co# (computed-observable ~hiccup-el)
         $el# (c2.dom/->dom ~el)]
    
     (singult.core/merge! $el# @co#)
     (~cf)
     (add-watch co# :update-dom
                (fn []
                  (singult.core/merge! $el# @co#)
                  (~cf)))
 
    co#))

I use this function with a callback that draws the connections.
The problem is that I currently resort to getting the slot elements by ID (generated in the first rendering pass from the original Clojure data) which brings all sorts of unpleaseant string manipulation, for instance I have functions like this

(defn in-elem
  [in]
  (.getElementById js/document (str "slotIn." in)))

That completely destroys the declarative awesomeness that is C2.

How can I do better?

Cheers!


Kevin Lynagh

unread,
Oct 6, 2012, 1:50:44 PM10/6/12
to c2-...@googlegroups.com

Now, I cannot draw the connections until the slots positions are known (i.e. the elements have been added to the DOM), hence my current workaround, bind-then!, that takes a callback function:

(defmacro bind-then!
  "Similar to C2's bind!, but takes a callback function that
  gets triggered after rendering"
    [el hiccup-el cf]
  `(let [co# (computed-observable ~hiccup-el)
         $el# (c2.dom/->dom ~el)]
    
     (singult.core/merge! $el# @co#)
     (~cf)
     (add-watch co# :update-dom
                (fn []
                  (singult.core/merge! $el# @co#)
                  (~cf)))
 
    co#))

I use this function with a callback that draws the connections.

Just FYI, the bind! macro returns a computed observable, which implements IWatchable.
Instead of maintaining your own fork of the bind! macro, you can capture the return value and add a watcher to that.
(Computed observables also implement their own IDisposable protocol so you can unhook them if you need to---see the source for more details: https://github.com/lynaghk/reflex/blob/master/src/cljs/reflex/core.cljs#L29).

 
The problem is that I currently resort to getting the slot elements by ID (generated in the first rendering pass from the original Clojure data)

Not sure if I follow the string manipulation problem you're discussing.
As for the 2-pass rendering in general, I don't know if there's any nice way around that; you're relying on the browser to do some layout calculations for you, and there's no way to do that in your application code without getting the DOM involved.
Adding a callback to the computed observable doesn't feel awesome, but I don't think you can do any better short of doing all of the layout calculations yourself and giving the DOM only absolutely-positioned elements.
(Though if you do want to go down that path, you might have luck with the cljs bindings to the Cassowary constraint solver: https://github.com/lynaghk/cassowary-coffee).

best,

Kevin

 

--
Kevin Lynagh
Keming Labs
http://keminglabs.com
888.502.1042

Reply all
Reply to author
Forward
0 new messages