Fork me on GitHub
#untangled
<
2016-09-16
>
tony.kay05:09:25

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: https://github.com/untangled-web/om-css

tony.kay05:09:30

OK, recipe complete, everything working.

gardnervickers12:09:19

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

gardnervickers12:09:48

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.

gardnervickers12:09:23

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.

tony.kay15:09:16

@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.

anmonteiro15:09:23

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

anmonteiro15:09:20

@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

tony.kay15:09:05

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

gardnervickers16:09:51

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

grzm17:09:40

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.

grzm18:09:33

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

tony.kay18:09:02

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

grzm18:09:35

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

tony.kay18:09:37

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

tony.kay18:09:42

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

tony.kay18:09:30

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

grzm18:09:07

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

tony.kay18:09:35

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.

grzm18:09:45

(om.next/app-state (:reconciler @myapp.main/app))

tony.kay18:09:01

something like that, yeah

tony.kay18:09:00

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

tony.kay18:09:34

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

grzm18:09:35

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

grzm18:09:52

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

tony.kay18:09:38

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

grzm18:09:41

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

tony.kay18:09:05

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

tony.kay18:09:23

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

tony.kay18:09:42

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)

tony.kay18:09:11

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

anmonteiro18:09:59

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

tony.kay18:09:42

Yeah, all sorts of exciting things to do 🙂

grzm18:09:59

you guys are amazing and inspiring 😉

tony.kay18:09:10

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

grzm18:09:18

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

mahinshaw18:09:22

I was just about to say that

anmonteiro18:09:41

if only days had 48+ hours

anmonteiro18:09:51

we could all shave more yaks

tony.kay18:09:19

or pick more low-hanging fruit

tony.kay18:09:46

some of this stuff is dead simple

grzm18:09:03

db->tree

(om.next/db->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.

grzm18:09:00

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

grzm18:09:20

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

grzm19:09:53

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

(defn app-query
  ([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)))))

grzm19:09:07

There's my helper function. Seems to work.

grzm19:09:49

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

tony.kay19:09:51

yep. that looks about right

tony.kay19:09:10

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

grzm19:09:33

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

tony.kay19:09:03

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.

grzm19:09:55

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

tony.kay19:09:08

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

tony.kay19:09:56

@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)

grzm19:09:22

Thanks, man 🙂

tony.kay19:09:48

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

tony.kay19:09:00

Just read the Om code near class->any

tony.kay19:09:57

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

tony.kay19:09:19

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

tony.kay19:09:30

@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

tony.kay19:09:49

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

tony.kay19:09:52

I should integrate those into the templates 🙂

tony.kay19:09:21

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."
  [ui-class]
  (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."
  [query-kw]
  (let [query (dump-query-kw query-kw)
        state @(om/app-state (:reconciler @app))]
    {:QUERY  query
     :RESULT (om/db->tree query state state)}))

tony.kay20:09:52

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]

tony.kay20:09:25

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

adambros21:09:08

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