Skip to content

Instantly share code, notes, and snippets.

@selfsame
Last active December 7, 2017 21:09
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save selfsame/2c7035a5f5f76ab72d6b to your computer and use it in GitHub Desktop.
Save selfsame/2c7035a5f5f76ab72d6b to your computer and use it in GitHub Desktop.
om next design question

how should I structure my component/queries?

My application interface is a set of generic panels that can be configured to display components via a :view prop. The views correspond to components that have a top level query. There could be 0-n instances of a component.

The panels themselves are normalized in the app-state:

{:panels/by-id {
   0 {:id 0 :view :outliner}
   1 {:id 0 :view :menu}
   2 {:id 0 :view :selection}
   3 {:id 0 :view :selection}}
 :panels [
    [:panels/by-id 0]
    [:panels/by-id 1]
    [:panels/by-id 2]
    [:panels/by-id 3]]}

I attempted to use a union query. My read fn is correctly pulling values for the union map, but subcomponent omcljs$path is nil. (guessing because :data is imaginary and perhaps the union query results should be under each :panels value)

(defui Main
  static om/IQuery
  (query [this] 
    `[{:panels [:id :view]}
      {:data 
       ~{:outliner (om/get-query Outliner)
         :selection (om/get-query Selection)
         :menubar (om/get-query Menubar)
         :query (om/get-query Query)}}])
  Object
  (render [this]
    (let [props (om/props this)]
      (html 
        (map 
          (fn [{:keys [view id]}]
            (dom/div #js {:key id}
              ((comp/onent view)    ;kw->factory 
               (get (:data props) view) )))
          (:panels props)))))))

How could I design this to work?

@selfsame
Copy link
Author

selfsame commented Feb 5, 2016

non union version

  • subcomponents have paths like [:outliner]
  • can transact! from all subcomponents
(defui Main
  static om/IQuery
  (query [this] 
    `[{:panels [:id :view :axis]}
      ;splicing {:view query} joins
      ~@(map #(apply hash-map ((juxt identity (comp om/get-query comp/camels)) %)) 
          [:selection :query :outliner :menubar])])
  Object
  (render [this]
    (let [props (om/props this)]
      (html 
        (map 
          (fn [{:keys [view id axis]}]
            (<div.panel.view (key id)
              ((comp/onent view) (get props view {}))))
          (:panels props)))))))
(pdfn read [env k params]
  {env (comp (is* :join) :type :ast)}
  (let [{:keys [query ast state]} env
        get-fn (if (om/ident? (:key ast)) get-in get)]
    {:value (om/db->tree query (get-fn @state (:key ast)) @state )}))

@selfsame
Copy link
Author

selfsame commented Feb 5, 2016

updating specific components

reconciler option :pathopt focuses updates (and parser reads) on components with om-ident

idents

(defui Selection
  static om/Ident
  (ident [this props] [:selection])
  ...

(defui Style
  static om/Ident
  (ident [this props] [:style])
  ...

transaction targeting specific components

(defn select-query-string [this]
  (let [[v n] (util/value-from-node this "selector")
        res (util/$$ v)
        uids (mapv #(.-uid %) res)]
    (om/transact! this
      `[(std/update-in ~{:path [:selection/uids] :fn #(do % (set uids))}) [:selection][:style]])))

pathopt

generic read for :query-root

(using https://github.com/selfsame/pdfn instead of multimethods)

(pdfn read [env k params]
  {env (comp vector? :query-root)}
  (let [{:keys [query ast state]} env
        root (get-in @state (:query-root env))]
    {:value (if query (om/db->tree query root @state) root)}))

@selfsame
Copy link
Author

selfsame commented Feb 5, 2016

starting to focus updates

Re implementing selection updates from the om.prev version. The outliner gets very large with document size, and had very specific updates. The outliner nodes ident to :uid->token which has a selection prop synced to the :selection/uids set. (also stores expansion, editing lock, visibility, and workspace mouse targeting booleans.)

render-counts

I was having issues focusing updates for recursive components, so the outliner is getting a flat list of nodes with a :depth prop. Perhaps it should be a tree of recursive :uid :children nodes that render a token subcomponent.

(defui Dom
  static om/Ident
  (ident [this _] [:uid->token (:uid (om/props this))])
  static om/IQuery (query [this] `[])
  Object
  (render [this] 
    (let [{:keys [uid children depth] :as props} (om/props this)
          {:keys [target selected] :as props} (if (:selected props) props (doc :uid->token uid))
          node-class (fp.util/class-str :node (? selected :selected) (? (expanded? token) :open))
          name-html (fast.util/html-prn-str uid)]
      (html
        (<div> (class node-class)
          (onClick [e] (click-node e uid this))
          (<span> (class "outliner_background" (? selected "selected")))
          (if children   
            (<span.icon (class (if (expanded? token) "expanded" "collapsed"))
              (onClick [e] (click-expand e uid this))))
          (<span.name "<" (dom/span #js {:dangerouslySetInnerHTML #js {:__html name-html}}) ">"
           (<span.uid (str uid )) (fp.util/render-count this)))))))
(comp/register :dom Dom)

(defui Outliner
  static om/Ident
  (ident [this props] [:outliner])
  static om/IQuery
  (query [this] `[{:dom/flat []}])
  Object
  (render [this]
    (html 
      (<div#outliner (fp.util/render-count this)     
        (map (comp/onent :dom) (:dom/flat (om/props this)))))))
(comp/register :outliner Outliner) 

general performance

om/issues/556 is no joke!

bootstrap.html parses to 1700 entries, and it's taking ~700ms just to transact the selection.

In lieu of transactions getting faster I'm going to limit the amount of data in the om atom. Finalpage has it's own normalized datatypes and protocols, and it's easier to compose when not cast as [:by-id 7] idents. uids can be cast to edn during read or render, and complex data (like the style selection to {rule {uid value}} map can be cached on selection update.

Other data that can be externalized

  • menu map
  • options and interface theme
  • unselected uncollapsed tokens (replace with nil, which signifies a clean token if it is mutated in the future)

@selfsame
Copy link
Author

selfsame commented Feb 7, 2016

tony.kay 23:54:14
@noonian: OK...lunch helped. I see where I was confused. I talked through it with coworkers and I see why my questions were confusing...your comment on the UI tree helped it fall into place. The app state is just some bag of data that changes over time. The UI tree is how I want to view that app state at any given time. Dynamic queries give us the flexibility to morph the UI in arbitrary ways, which may involve arbitrary queries to the server at any time (which if they update app state will trigger re-renders).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment