Fork me on GitHub

I went ahead and split these functions out and put them in a library. All the tests pass, but I have not written an app specifically with the library just yet:


OK, recipe complete, everything working.


Very cool, we were just talking about how to manage CSS on our components.


We have a component hierarchy, think list, item, sublist, that is used in many different places across our app to display this data in different ways. Each instance where we use it However also requires extra data on top of the common set. Is it right to try think about each usage instance as a different set of data being queried for, since there is extra data on top of the common set? Or should we try "subclassing" here? I'm interested to hear if anyone has any thoughts on this.


I can see a couple different ways to achieve something like inheritance, but they seem messy. Maybe just defining the common query bits as regular data and merging the common bits into each components query with their unique bits.


@gardnervickers It's kind of up to you on the data front. You could combine the data together in to the objects in the db tables, and just query for what you need where you need it. If the "other" data is in a different format that requires more complex joins you might consider querying both in the component and doing the combination via helper functions in the UI. Lots of approaches. The trade-offs really depend on the data.


if that additional data is common to every component, you could also maybe solve that by using links


@gardnervickers @tony.kay I also think that problem touches a little on some thoughts I’ve been having. it seems to me that’s a very nice use case for the existence of mixins


@anmonteiro we should talk about that sometime. I'd be interested in brainstorming about the use-cases and approaches


Combining the data together into objects in the DB tables is the approach we went with which has worked out quite well.


Is there a way to get at the parser/reconciler for an untangled client app? I'd like to run some arbitrary queries against it in the repl, trying to figure out why data isn't propagating to components.


seems like every time I move components around I break something 😕


@grzm Are you using InitialAppState? ...that helps


That's one more thing to check. Thanks for the reminder


Running arbitrary queries is simple. Basically use db->tree on your app state


there is an app-state function, I believe, for untangled client. The "parser" is just db->tree with minor add-ons to deal with things that happen for UI refresh queries that don't run from root


We've found the co-location of initial app state in the components saves us a ton of work. I highly recommend it.


Yeah, that's what I've been doing.


The only place you get bitten is on server data that you don't integrate into the db correctly, and using untangled-spec to verify those functions work based on sample data is recommended as well.


( (:reconciler @myapp.main/app))


something like that, yeah


I'd write a helper function and put it in the user.cljs file


something that pprint's the output of db->tree against the Root query


Yeah, that part of the app hasn't changed. I added a leaf component, updated the former leaf query to use get-query on the new leaf


Yeah, that makes sense. The log-app-state helper is very helpful


The only other gotcha that I've seen is if a component ONLY has a link query (e.g. [ [:a '_] ]) then you MUST have something in the app state where that component lives (e.g. an empty map) or db->tree won't go into the component at all


Don't have any link queries, so that's likely not it.


I want to make some kind of Chrome tool Om DB debug tool


like, click on a component and see it's composed query, view app-state tables, run arbitrary queries


just haven't found the time. Figwheel has that nice "jump to source" feature that would be fun to leverage on component clicks, too (imagine a kb shortcut like CTRL-J that turns on click detection for UI components, then jumps your editor to the source)


@adambros did some proof-of-concept on that one


@tony.kay I’ve also been thinking about that


Yeah, all sorts of exciting things to do 🙂


you guys are amazing and inspiring 😉


Do you happen to know if anyone has written a Chrome plugin with cljs?


I'm happy when I get a form to have good validation 🙂


I was just about to say that


if only days had 48+ hours


we could all shave more yaks


or pick more low-hanging fruit


some of this stuff is dead simple



(>tree query some-data app-state-db)
Given a query expression, some data in the default database format, some application state data in the default database format, denormalize it. This will replace all ident link nodes with their actual data recursively. This is useful in parse in order to avoid manually joining in nested relationships.


I'm really hazy on the distinction between some-data and app-state-db. How are they different? (I'm not doubting they are)


actually, that's more of an om question, isn't it


(defn app-state [] (om/app-state (:reconciler @main/app)))

(defn app-query
   (let [state @(app-state)]
     (pprint (om/db->tree query state state))))
  ([query path]
   (let [state @(app-state)]
     (pprint (om/db->tree query (get-in state path) state)))))


There's my helper function. Seems to work.


And it's confirming for me that something's wrong 🙂


yep. that looks about right


the some-data part is because the alg is recursive and can pass itself subsets of the db, if I remember correctly


Yup. Some gentlemen in #om helped me attain a bit more enlightenment, at least momentarily 🙂


there's also a nice function in Om called focus-query I think. You can use that to prune off bits of the query that you don't care about, which could make it a bit easier to read your result.


There's so much goodness around all of this, and it's just so hard for me to find 😕


e.g. you give it a path, and it trims all the cruft except that path


@grzm This will give you the focused query to a specific class (if it has multiple paths it won't work right, but often useful)


Thanks, man 🙂


just pass it a class. The other indexer functions can be helpful too


Just read the Om code near class->any


ref->any is handy if you can target by some keyword that is in the component's query: (om/ref->any (:reconciler @app) :label) will return some component that has :label in it's query


If you namespace your props well, then that one can be quite useful


@grzm This one lets you do (dump-query-kw :user/name) (and works well if you know only one such component is on screen that queries for that kw


combine those into more helpers and you should be debugging like a champ


I should integrate those into the templates 🙂


for completeness:

(defn dump-query [comp]
  (let [component (om/class->any (:reconciler @app) comp)]
    (om/full-query component)))

(defn dump-query-kw [kw]
  (let [component (om/ref->any (:reconciler @app) kw)]
    (om/full-query component)))

(defn q
  "Run the query of the given UI class and return the result as a map of the query that was run and the data that was returned.
  NOTE: If the component is mounted in several places on the UI, you may not get the expected result. Be sure to check
  the QUERY part of the result to see the query used."
  (let [query (dump-query ui-class)
        state @(om/app-state (:reconciler @app))]
    {:QUERY  query
     :RESULT (om/db->tree query state state)}))

(defn qkw
  "Find a component that uses the given keyword in its query, then run that query against the app database and show
  the result. NOTE: If more than one component matches, your results may vary. Be sure to examine the query that
  was used."
  (let [query (dump-query-kw query-kw)
        state @(om/app-state (:reconciler @app))]
    {:QUERY  query
     :RESULT (om/db->tree query state state)}))


I've pushed version 1.0.0 of our untangled template to clojars. You should now be able to do: lein new untangled dirname to generate an untangled project. Options to include extra stuff:

lein new untangled $projectName -- [:devcards] [:server] [:all]


not sure how well the options work, but I've at least reasonably tested the default client generation


ping me for untangled-template bugs/questions/w.e as i wrote most of it