Fork me on GitHub
#om
<
2016-09-16
>
ag00:09:05

I’ve been using Om.Next for awhile, and I have many places in the app where I use ui components with no queries. So if I am have props that being pulled by parent component and passing them directly to a factory of child, and inside render of the child grabbing props with (om/props this), are there any benefits of having query that explicitly states what that component needs?

ethangracer01:09:26

@ag yes, especially if you use path optimization. You get the added benefit of not having to rerender the entire app from the root each time that the app state is modified. For larger apps, it also means you don't need a root component with a gigantic query. You can reason locally within the component to understand what data it needs without having to think quite as much about how to get the data there

ag01:09:35

hmm ok. what’s the best way to deal with situation when I have a component that needs to render a few children but not depending on data, it would use the same data but should be rendering things differently, right now I’m doing something like this, which I guess (although works) still totally wacky:

(ui-filter-popover {:children [ui-filter-text ui-filter-date]}) 
…

 (ui-filter-popover {:children [(ui-checkbox-group types)]})
basically I'm passing children as props

tony.kay05:09:17

I’ve added a library that will work with Om Next and Untangled to co-locate localized CSS on reusable components. It does not need to hack defui, can refresh during development, and automatically gives you composition and minimal CSS for the components you use. It is similar to the library om-css from Antonio, but it doesn’t require you use modified dom or defui components, and does not need to output a file. https://github.com/untangled-web/om-css

niamu05:09:26

@tony.kay Excellent library. Looking forward to playing around with it. Always wanted a way to do exactly that and had thought of writing it myself many times. Glad someone else beat me to it.

levitanong07:09:42

@tony.kay Cool! Any word on how this could work with https://github.com/postcss/autoprefixer?

mitchelkuijpers08:09:24

@tony.kay om-css from Antonio does refresh?

anmonteiro11:09:13

@mitchelkuijpers: yeah you can use it with Figwheel

mitchelkuijpers11:09:33

@anmonteiro I use it with boot 🙂

anmonteiro11:09:56

And it doesnt refresh?

mitchelkuijpers11:09:45

With some hackery

anmonteiro11:09:47

Oh I thought you were asking if it refreshed

anmonteiro11:09:05

Well yeah it does, and @tony.kay is aware of it

mitchelkuijpers11:09:16

No is misread tony.kay his post, I thought it stated that your’s does not

mitchelkuijpers11:09:30

I made om-css reloading working by doing a bit of a hack hehe:

(sift :move {#"^public\/js\/main\.outout\.css$" "public/css/next.css”})

anmonteiro11:09:15

Oh it might get prefixed by Boot-cljs automatically

mitchelkuijpers11:09:29

Yeah that is the problem, i don’t really care

mitchelkuijpers11:09:02

I first used the

:css-output-to "resources/public/main.css”
but that triggers double reloads

mitchelkuijpers11:09:09

or it would reload 4 or 5 times 😛

mitchelkuijpers11:09:19

because boot is watching the resources folder

anmonteiro11:09:09

@mitchelkuijpers: probably worth submitting a PR to the readme explaining how to work with Boot

mitchelkuijpers11:09:23

@anmonteiro I will do that tonight

tony.kay15:09:54

@mitchelkuijpers The version from @anmonteiro uses overridden defui and DOM functions to ensure that the syntax is easy, and that a CSS file is (re)generated as you evolve the CSS (which figwheel) can hot reload. I actually worked with him via PM on the design of mine to address some issues I had (I didn't want to hack defui and DOM macros/functions). The resulting library has some nice facets as a result.

mitchelkuijpers15:09:34

@tony.kay Cool! I am sorry I misread the post a bit

tony.kay15:09:22

@levitanong The library doesn't really care what you use for CSS. In fact, you could just co-locate strings and avoid some of the helper functions. I added helpers for garden, but we could just as easily add helpers to combine anything that can make CSS.

mitchelkuijpers15:09:32

I do wonder if the compose to root constraint is nice because I am currently using the other om-css, but I would be interested to see what are exactly the benefits of composing to root

mitchelkuijpers15:09:48

What I mean to say I would not mind to be proven wrong

tony.kay15:09:06

Another cool thing: you can use React component lifecycle (e.g. component will mount) to inject component styles into the DOM..meaning a library of widgets could inject their styles with no extra config!

mitchelkuijpers15:09:40

So you don’t generate a css file?

tony.kay15:09:40

@mitchelkuijpers which addresses the compose to root issue...not actually necessary

tony.kay15:09:11

nope, you either manually include a (dom/style) element in your tree, or use upsert-style to inject a style element into the DOM body

tony.kay15:09:44

and since it is all in code, users of components could "theme" or otherwise configure the CSS through API hooks of the component in question

mitchelkuijpers15:09:47

Thanks @tony.kay I will try it out, but I wont switch anytime soon prob because we are already pretty invested in the other om-csss

tony.kay15:09:01

oh, it's all good. Just giving alternatives 🙂

tony.kay15:09:30

Most of the work was done by @noprompt on garden.

mitchelkuijpers15:09:42

Cool, that is a great thing. I have stolen a lot from untangled

denik15:09:56

@tony.kay does this work with sablono?

tony.kay15:09:22

Oh, and a working Untangled Recipe in the cookbook is now up on github. @denik See my comment above to levitanong

anmonteiro15:09:44

@tony.kay I’m not sure you understood @levitanong’s question

anmonteiro15:09:02

I think it’s also something I mentioned to you yesterday

tony.kay15:09:14

Oh. browser prefixing?

anmonteiro15:09:42

the problem being that a lot of people now process their CSS, either with Less/SASS and/or PostCSS that adds vendor prefixing to CSS rules

anmonteiro15:09:01

this is why it was a hard requirement for om-css to generate a CSS file

anmonteiro15:09:10

so that it could be included as part of the “CSS build process"

tony.kay15:09:30

Ah, I see. Yeah, I'm not much of a CSS guru 😉

anmonteiro15:09:47

I really like the idea behind your newfound approach to colocate CSS with Om Next

anmonteiro15:09:04

however, I think there’s still space for a solution that combines the best of both worlds

tony.kay15:09:08

but my response stands: whatever library you want to use to generate the actual CSS doesn't matter. The library is more about the idea of how to inject it and compose it.

tony.kay15:09:02

Ah, you're right, I didn't understand he was asking about postcss external css parsing tools

anmonteiro15:09:03

I just haven’t had enough time to look at what exists out there in the React world, such as js-css, aphrodite and stuff

anmonteiro15:09:27

we could probably learn a lot from those

tony.kay15:09:53

it would be straightforward to have a pre-processor work on the garden data structures (or the resulting css strings?) and do a similar thing. Not aware of existing libs though.

anmonteiro15:09:25

right, but my point was to find something that could work with existing tools

anmonteiro15:09:35

not to reinvent all of them 🙂

tony.kay15:09:58

ready...set.....GO!

denik15:09:45

@tony.kay still wondering if localize-classnames could break when using sablono

tony.kay15:09:10

oh, @denik it probably won't work quite right

tony.kay15:09:40

since it specifically looks for #js maps and specifically rewrites entries in those from .class to .className

tony.kay15:09:50

I would imagine it would be a minor change to make it work (or supply a macro for sablano). It's just a walker with a transform in specter, so any nested data structure can be scanned (in fact the tests use more raw data to make it easier to run the test in untangled-spec)

tony.kay15:09:36

But the macro is for convenience, as the docs state. You can just get the localized names directly from local-class

tony.kay15:09:09

It also looks like garden does support some auto-prefixing, just FYI: https://github.com/noprompt/garden/wiki/Compiler

denik15:09:15

yep, saw that & agree. Thanks @tony.kay

tony.kay15:09:57

be happy to accept a PR for sablano if you care to make one

anmonteiro15:09:04

@dnolen I’ve been thinking about an optimization to reconcile!. I think it’s the case today that if a number of components are queued for re-render, all of them will run through the update loop. It does seem to me, however, that this loop is quite naïve. I think we have enough information to know whenever we have already updated a subtree from the top, in which case the components that are below in that subtree could just be removed from the render queue.

peeja15:09:24

What does :elide-paths do? Is that internal, or user-facing configuration?

anmonteiro15:09:30

@peeja :elide-paths is a parser option that will skip adding Om path metadata to the results of parsing

anmonteiro15:09:40

you can expect things to break if you set it to true 🙂

peeja15:09:53

Noted. 🙂 Thanks.

peeja15:09:05

Is that something you might use server-side?

anmonteiro15:09:43

@peeja yes, definitely! I had never thought about it

anmonteiro15:09:44

we should probably make that the default as well when invoking it from om.next.server

anmonteiro16:09:31

it’s already there

peeja16:09:37

@anmonteiro Is there an example app that uses Compassus with a backend? I feel like I'm misunderstanding how it's supposed to work. Specifically, I don't understand why all the queries are keyed by route. It seems to me like the parser and especially the remote should be ignorant of the routes.

anmonteiro16:09:15

@peeja you can probably look in the Compassus test suite

anmonteiro16:09:27

nothing open source with a server that I know of

anmonteiro16:09:01

the remote can definitely be unaware of the routes

anmonteiro16:09:09

and I think that’s the default, actually

peeja16:09:19

Yeah, I guess it is

anmonteiro16:09:24

you’d need to use compassus-merge otherwise

anmonteiro16:09:59

hopefully these should guide you

anmonteiro16:09:17

I should definitely make an example fullstack application that showcases Compassus

peeja16:09:22

It's more that I would have expected each page's query to run against the root, rather than at the route key, and because it's nested under the route key I'm having trouble raising idents to the top for my remote

peeja16:09:28

Maybe I just have to wrestle with it some more

tony.kay16:09:57

@levitanong One more comment on auto-prefixing. Not only does garden support adding vendor prefixes to tags, it doesn't need to do the same checking as postcss. It can just emit them wherever you tell it to, even if the browser in question doesn't need them. The cool thing about generating this stuff client side is the the download size isn't affected at all, since the CSS is being generated client-side. Vendor prefix the heck out of things, and don't worry about size.

anmonteiro16:09:08

@peeja do you have anything sizeable working with Compassus server-side that you can open source?

anmonteiro16:09:25

happy to link to it in the absence of a fullstack example

anmonteiro16:09:04

@peeja yeah the vector within a vector is no problem at all

anmonteiro16:09:22

instead of using query->ast try using om.next.impl.parser/expr->ast

anmonteiro16:09:25

should solve that one

peeja16:09:32

Yeah, but there may be multiple keys

peeja16:09:40

I need a sort of mapcat thing

anmonteiro16:09:04

hrm, I think I’ve solved that one before, let me look around

anmonteiro16:09:55

@peeja have you tried tweaking merge-sends?

peeja16:09:00

Compassus's read for [:default ::route-data] for instance does (parser/expr->ast (first ret)), but my ret will have multiple values and I want them all

anmonteiro16:09:01

^ reconciler option

peeja16:09:07

I haven't. What does that do?

anmonteiro16:09:34

it’s how you merge the sends your parser returns with the ones that are already queued by the reconciler

peeja16:09:45

Oh, interesting…

anmonteiro16:09:53

@peeja I’m not sure you’ll have more than one

peeja16:09:31

I will if the query I give to the parser (to get ret) has more than one read in it

anmonteiro16:09:52

@peeja again, not sure that’s the case

anmonteiro16:09:15

the query that Compassus passes to the user-parser is a single join: [{route the-query}]

peeja16:09:26

Right, I'm talking about my code

peeja16:09:36

My (user) parser is calling itself

anmonteiro16:09:48

I didn’t get what you meant then, sorry

anmonteiro16:09:57

I was thinking there might be a bug in Compassus there

peeja16:09:07

Ah, no, Compassus is doing what it does fine 🙂

peeja16:09:34

This app just not be compatible with Compassus's take

anmonteiro16:09:55

@peeja well it will be at the top-level, right?

peeja16:09:57

because I can't imagine a use case for the page's reads being nested under a key for the route

peeja16:09:22

Or, to put it another way, I can't imagine a page having a query that isn't solely idents (with joins)

peeja16:09:52

which I thought wouldn't be a problem, because I thought idents automatically moved to the top of the query, but apparently I have to do that myself

peeja16:09:21

That is, db->tree will pretend they're at the root of the query, but that doesn't help when I need to pass the query on to my remote

anmonteiro16:09:26

sorry I didn’t follow since the double negative 🙂

peeja16:09:06

My page queries are all made up of idents, and I can't imagine why they would have anything else

peeja16:09:47

Here's the use case, specifically:

peeja16:09:57

I'm spiking on recreating this page with Om Next: https://circleci.com/projects

peeja16:09:26

So the page needs the current user's orgs: that's {[:app/current-user '_] [:user/organizations]}

peeja16:09:04

and it needs more info on whichever org is selected, which right now looks like {[:organization/by-vcs-type-and-name {:vcs-type "github", :login "circleci"}] [:organization/login]}

peeja16:09:21

(pardon the fact that the ident there uses a map as its value, which I know is technically illegal)

peeja16:09:11

(and eventually that will actually query via some kind of routing data, so it'll be {[:app/routing-data '_] […]])

peeja16:09:31

In any case, the query is always made up of joins against idents

peeja16:09:16

because at this level it's only interested in global stuff, and only subcomponents will be interested in relative data

anmonteiro16:09:40

I think it all makes sense at a first sight

anmonteiro16:09:57

so what’s the problem specifically?

anmonteiro16:09:03

currently just remoting?

peeja16:09:48

Exactly. Because it's on a page, my query is

[{:app/projects [{[:app/current-user _] [:user/organizations]}
                 {[:organization/by-vcs-type-and-name {:vcs-type "github" :login "circleci"}]
                  [:organization/login]}]}]

peeja16:09:30

I'm not sure how to turn that into something remotable

anmonteiro16:09:58

@peeja so app/projects isn’t the route?

peeja16:09:00

I mean, I guess I can deal with it in the send, but I thought it would be more correct to do it in the parser

peeja16:09:08

It is, but the remote doesn't care about that

peeja16:09:18

Oh, and the most important part here: the API is RESTful

peeja16:09:44

I need to turn this into a call to get the current user's orgs, and a call to get the login (etc) of the selected org

anmonteiro16:09:04

@peeja OK so there are different problems here

anmonteiro16:09:10

let’s tackle each one at a time

anmonteiro16:09:57

1. the RESTful call could be solved by having a multimethod in the send, which IIRC you already have

peeja16:09:12

Yeah, that's what I'm going for

anmonteiro16:09:30

then it makes total sense to return the route to dispatch on that multimethod

peeja16:09:38

Yeah, I guess that works

anmonteiro16:09:02

so Compassus will give that whole query to the send method if I’m understanding correctly

peeja16:09:04

It feels weird to me that the send knows about my UI-only concerns, but… eh

anmonteiro16:09:20

and it should

anmonteiro16:09:42

what are the UI-only concerns that send knows about?

peeja16:09:48

:app/projects

anmonteiro16:09:39

from the moment that you have a REST API, those are not UI-only concerns anymore

anmonteiro16:09:57

it’s not a problem of your frontend, it’s just a problem of REST’s out-of-band-ness and coupling

peeja16:09:21

But it doesn't matter which page I'm on. Any page might declare it's interested in the current user's orgs

peeja16:09:36

I should only have to change my page component's query

anmonteiro16:09:10

@peeja isn’t that what I’m saying? Wouldn’t that all be great if only you weren’t communicating with REST endpoints?

peeja16:09:22

I'm not following

peeja16:09:40

If I were hitting a single queried API, I wouldn't want that to know about :app/projects either

anmonteiro16:09:07

if you were hitting a single queried API, you could just send what’s beneath :app/projects in the join

anmonteiro16:09:21

or any other route key, for that matter

peeja16:09:26

Right. So whose job is it to extract that: the send or the parser?

anmonteiro16:09:27

without having to dispatch based on the route

anmonteiro16:09:30

@peeja I would probably do it in send because it’s easier

peeja16:09:34

My understanding was that the send shouldn't receive UI concerns from the parser

anmonteiro16:09:49

but it would be simpler to do it in the parser

anmonteiro16:09:26

so to do it in the send function, you probably already have everything hooked up

anmonteiro16:09:42

to do it in the parser you’ll have to mess with merge-sends if I understand correctly

peeja16:09:29

Mess with it…to have it treat [[:key/one :key/two]] as [:key/one :key/two]?

peeja16:09:53

That seems neither simple nor easy 🙂

peeja16:09:04

That feels like a type mismatch

peeja16:09:14

Anyway, that's still really helpful. You've been super generous with your time, as usual, and I greatly appreciate it.

anmonteiro16:09:28

try this for merge-sends:

(defn custom-merge-sends [a b]
  (merge-with #(into %1 (mapcat identity) %2) a b))

anmonteiro16:09:58

and report back!

anmonteiro16:09:55

@peeja pinging, not sure you saw this ^

peeja16:09:41

Ah, haha, that doesn't work because merge-withing {} with anything won't use the into and won't use the mapcat 😛

peeja16:09:57

Just went deep adding debug statements to figure that out

anmonteiro16:09:08

OK let me revise that

peeja16:09:22

Oh, yeah, I can still just do it to everything on the way in, yeah?

anmonteiro16:09:40

@peeja

(defn custom-merge-sends [a b]
  (let [a (cond->> a
            (not (every? (set (keys a)) (keys b)))
            (merge (zipmap (keys b) (repeat []))))]
    (merge-with #(into %1 (mapcat identity) %2) a b)))

peeja17:09:35

It works!

peeja17:09:45

I did

(fn [sends new-sends]
  (let [unwrapped-new-sends
        (into {} (map #(vector (key %) (first (val %)))) new-sends)]
    (merge-with into sends unwrapped-new-sends)))

peeja17:09:05

(which naively assumes everything needs unwrapping)

peeja17:09:05

I still feel pretty weird about returning an invalid query from the parser, but at least this works

anmonteiro17:09:15

@peeja glad it’s working

anmonteiro17:09:21

the problem is just the recursive parsing

peeja17:09:27

"just" 😄

anmonteiro17:09:53

I created a gist with my version above so I don’t forget it if I ever end up needing it 🙂 https://gist.github.com/anmonteiro/1dea0b30ebcc195875dc5d0b102f69c8

peeja17:09:02

:thumbsup:

peeja17:09:10

Huh. I wonder if I should use process-roots for this…

peeja17:09:28

I could actually change the query on the way in to the parser, and skip the route keys entirely

peeja17:09:44

Maybe that's even weirder…

jfntn17:09:27

Funny I’m running into this very problem and was looking at process-roots myself

jfntn17:09:54

Not very conclusive so far, still not sure if it’s a valid approach?

jfntn17:09:21

Using datomic here, so there’s a strong incentive to produce a valid query in the parser

anmonteiro17:09:35

@jfntn the approach @peeja & I came up with will send a valid query to the remote

peeja17:09:19

Right now I'm playing with wrapping the parser itself, so that while my read returns something funny, my parser returns the correct send

petterik17:09:54

I'm wrapping my read and mutate's in "middleware" so I can edit or validate inputs/outputs. I also have some parser middlewares. I don't know how common this is

petterik17:09:17

not for this specific usecase, but it's a common thing I do to solve problems around parser/reads/mutates

grzm18:09:30

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:46

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

env19:09:46

@grzm: this function is internally called denormalize*. some-data is data that contains references / links to entities app-state-db is all the app’s data, stored in a normal form. this function recursively replaces those links/references with the actual data using query and the data in app-state-db.

grzm19:09:22

@env the app-state-db contains all off the state, though, right? I don't understand what some-data could add.

env19:09:52

@grzm, some-data defines the tree of data to recurse over and the shape of the output, while app-state-db is the source of data in which to look up links. in simple cases these, could be the same, but structuring the params in this way allows for the case where the desired output is different than the full db tree, or perhaps not part of the db tree at all (but still using links to data that is in the app-state)

peeja19:09:44

@grzm For instance, it's common for a read implementation to look like:

peeja19:09:49

(let [st @state]
  {:value (om-next/db->tree query (get st key) st)})

peeja19:09:25

Because query isn't a query against the root of the state, it's a query against (get st key)

peeja19:09:42

(but you still need the root of the state to look up idents)

grzm19:09:43

yup. In that case I don't see what the purpose of the app-state-db is. All of the data pertinent to the query should be in the some-data portion.

grzm19:09:22

@peeja idents in the query, correct?

grzm19:09:34

okay. That makes sense.

peeja19:09:38

Well, or in the state

peeja19:09:05

If (get st key) contains links, those links are dereferenced from the top of the app state

grzm19:09:07

because the data is normalized

peeja19:09:11

You've got it

grzm19:09:18

Let's hope it sticks 🙂

peeja19:09:23

I know the feeling 🙂

grzm19:09:41

Thanks for your patience, guys 🙂

peeja19:09:27

Which is because, once one of these reads can have multiple joins/keys, Compassus has the same problem I'm trying to solve.

peeja20:09:24

If I query for a singleton ident—`[:foo _]`—I should expect it to show up in the props as :foo, and if I query for a non-singleton ident—`[:foo 5]`—I should expect it to show up as the full ident, [:foo 5]. Is that right?

peeja21:09:49

When new data is merged, what components re-read?

peeja21:09:31

If I understand correctly, the components that re-read are determined by the merge function's :keys value

peeja21:09:44

but I'm not clear on what those keys correspond to.

peeja21:09:56

How does the indexer match those keys to components?

peeja22:09:16

It looks like my component which queries for [:app/current-user _] is being indexed by the key :app/current-user, but not [:app/current-user _], which means that when my send comes back it doesn't cause it to re-read.

peeja22:09:14

Judging by this, I'm guessing the indexing is intentional, and my send is acting funky: https://github.com/omcljs/om/blob/master/src/main/om/next.cljc#L1774

peeja22:09:40

Looks like this may be something that compasuss-merge isn't handling correctly

anmonteiro22:09:07

@peeja the actual issue is that Links were never meant to be sent server-side 🙂

peeja22:09:25

I'm not sending links server side

anmonteiro22:09:44

so I’m misunderstanding your issue

peeja22:09:10

I get some orgs via the API, and then I call (cb (rewrite {:app/current-user {:user/organizations (vec orgs)}}) query)

peeja22:09:36

Oh, I see, are you saying idents were never meant to reach the send?

anmonteiro22:09:01

is rewrite some process-roots thing?

peeja22:09:09

Yep, that's right

anmonteiro22:09:15

that might be where the problem is?

peeja22:09:44

Maybe a better question is: what's the correct form for the data when I call the cb?

peeja22:09:51

What does the default merge expect?

anmonteiro22:09:30

that’s a tough one to answer thouroughly

anmonteiro22:09:04

what I would say is that the callback probably doesn’t expect a link

peeja22:09:43

(rewrite {[:app/current-user '_] {:user/organizations (vec orgs)}}), I get

{:app/projects
   {[:app/current-user _]
    {:user/organizations
     [{:organization/name "circleci"}
      ...]}}}

peeja22:09:14

With (rewrite {:app/current-user {:user/organizations (vec orgs)}}), I get

{:app/current-user
   {:user/organizations
    [{:organization/name "circleci"}
     ...
   :app/projects {[:app/current-user _] nil}}

anmonteiro22:09:16

so the thing is that :app/current-user is supposed to be a top-level key in your app state

anmonteiro22:09:42

so you want to merge that in a top level key, not under :app/projects

anmonteiro22:09:00

my other question is why the send function is even getting the link?

anmonteiro22:09:42

they’re not really meant to be there

peeja22:09:51

So, how am I supposed to ask for the orgs which belong to the current user?

anmonteiro22:09:10

I would expect current-user to be only a pointer in your app state

anmonteiro22:09:46

right? a pointer to an actual [:user/by-id id] or something

peeja22:09:59

Okay, how do I ask for that user's orgs?

peeja22:09:02

without using a link

anmonteiro22:09:39

I’m probably not thinking clearly about all the solutions (a bit tired, we can probably revisit this tomorrow)

anmonteiro22:09:49

but one would be to modify the AST in your parser

anmonteiro22:09:13

such that it includes the actual user ID

peeja22:09:21

I mean, if the remote was queried, even backed by Datomic, what would an ideal query look like over the wire?

peeja22:09:42

if it didn't include [:current-user _]

peeja22:09:13

Anyway, I guess I'll sleep on it and maybe have an epiphany. 🙂

peeja22:09:25

Thanks for the help, as always