Fork me on GitHub
#fulcro
<
2017-08-15
>
currentoor00:08:46

@tony.kay if i wanted to wrap the parser functions, how would i do that?

currentoor00:08:25

used to be you could pass in the parser into make-untangled-server

:parser (om/parser {:read   api/parser-read
                                            :mutate api/parser-mutate})

currentoor00:08:01

i basically have some parser “middleware” that i want to execute on every parser read/write

tony.kay00:08:08

you still can. It accepts a parser option

tony.kay00:08:53

@currentoor oh, do you mean you’d like to use defquery and defmutation as well? There are multimethods. Wrap those with your own functions, make a parser, and pass it in.

tony.kay00:08:20

actually, there is one multimethod for mutations (fulcro.server/server-mutate) and server-read is a function that dispatches to two diff multimethods

tony.kay00:08:01

so, just make a parser by wrapping those (om/parser {:read (fn [env k p] … (server-read env k p)) …

tony.kay00:08:13

and pass that into the make-fulcro-server

currentoor01:08:48

@tony.kay how about with the not easy server?

tony.kay01:08:21

same thing. There it is more obvious, because you have to make a module that dispatches

currentoor01:08:22

fulcro.server/fulcro-system

tony.kay01:08:23

same exercise

tony.kay01:08:45

just wrap the predefined server-read and server-mutate with your own fns, and supply those to the module

tony.kay01:08:04

or you can not use the provided ones at all…doesn’t matter. They’re just for convenience

tony.kay01:08:59

Yeah, I was just adding some convenience macros for writing queries and mutates on the server, but if you don’t want to use those, nothing is different at all. They were adds, not changes

tony.kay04:08:14

Just released Fulcro 1.0.0-beta8 Remember that if you use spec, you must use at least beta7

claudiu17:08:10

is there a better way than "om-computed" for doing listing with odd rows highlighted differently ? 🙂

gardnervickers17:08:05

@claudiu CSS even/odd rules might help if you’re just after highlighting. If you need different behavior based on index, computed is probably your best bet short of setting something like :ui/index on each element in a mutation.

claudiu17:08:17

@gardnervickers cool 10x, totally forgot about css rules (been a while since I wrote css). 🙂

claudiu17:08:54

computed it is, since I might have the case of same component in two listings ui/index would override each other

tony.kay17:08:00

also remember you can always make your factory do the computed part for you for a bit of syntactic sugar: (defn ui-row [props & {:keys [highlight]}] ((om/factory Row) (om/computed props {:highlight highlight}))) -> (ui-row row :highlight (odd? idx))

tony.kay17:08:53

(I actually tend to let the factory around the defn for a bit more efficiency…but the concept is the same)

tony.kay17:08:46

(let [f (om/factory Row {:keyfn :db/id})]
  (defn ui-row [props & {:keys [highlight]}]
    (f (om/computed props {:highlight highlight}))))

claudiu17:08:57

that looks a lot nicer and clearer 🙂

claudiu17:08:10

don't know exactly how this works. Is there any difference between adding the defn outside of defui or in the render let ? (wondering if functions will be recreated when the component updates)

tony.kay17:08:01

you don’t want to re-run defn within the body of a function, unless you actually want to be able to dynamically change that code over and over

tony.kay17:08:08

So, outside of defui for sure

tony.kay17:08:23

but at the top-level of your file, you can wrap a defn in a let

tony.kay17:08:34

that will only run when the file first evals (when loaded)

tony.kay17:08:13

It is a dynamic language. If you re-run a defn, it redefines the function. At the file-level, everything is evaluated on load. Mostly you do non-side-effecty things (like define functions)…but you can also just run whatever code you want. Remember that while def and such might be special forms or macros, then are still just forms, and the language itself is perfectly happy to run those forms where/when it finds them.

tony.kay17:08:11

The problem with running a defn inside of something that has not run yet should be obvious: the function won’t be defined until that thing runs. This leads to your program changing over time in a way that is more path-dependent than most people prefer…for good reason

tony.kay17:08:51

But a let is really just a way of naming things for a context (eval environment)

claudiu18:08:50

was a bit confused by the (let don't remember seeing it in the top level so far 🙂 but makes total sense, no need for f to be module level. 🙂

tony.kay18:08:30

module? Not sure that’s what you mean. Namespace?

tony.kay18:08:20

Yeah, the let at the top level is surprising to most of us that come from languages with tons of “syntax”. It’s funny how a langauge with almost no syntax leads to such surprises…the syntax fits on a single page: put forms in a file 🙂

tony.kay18:08:51

I suspect your horizons just expanded considerably 🙂

sundarj18:08:34

i thought i had closures all figured out, but i would've never thought of using them like that!

claudiu18:08:53

ahh yes I meant namespace. Yep pretty much, every day amazed by clojure. Now that I'm actually building a production project, it's even more impressive. A ton of small things like this and declare, that are really nice.

tony.kay18:08:02

I am kind of fond of it, actually. Better than “private”. Completely isolates the definition to a one-time run, and a very local scope.

tony.kay19:08:15

I picked it up from “The Joy of Clojure” by Fogus and Houser

wilkerlucio19:08:38

awesome book 👆

currentoor19:08:23

So I used to have something like

(f/load-data reconciler [{:dashboards (om/get-query DashboardTableRow)}
                                                        {:reports (om/get-query Report)}
                                                        {:current-user (om/get-query User)}]
                                               :post-mutation 'post-initial-load)
But since we don’t have load-data anymore, how should I go about doing this? Make a new root query that includes all three of these?

currentoor19:08:55

or a component that queries for these three?

tony.kay19:08:59

No, three calls to load, with the post-mutation on the last one (or the one it most relates to)

tony.kay19:08:13

the networking layer will join them

tony.kay19:08:26

into one request if they are compatible (not using the same root key)

tony.kay19:08:47

networking cannot go until you release the thread…so multiple calls to load can issue a single net req

currentoor19:08:01

oh wow that’s cool

tony.kay19:08:07

(load app :dashboards DashboardTableRow)

tony.kay19:08:32

it makes the load story cleaner…the post mutation might be needed on a sepcific bit, and this makes it more obvious…also targeting with :target can avoid many post mutations, but can only apply to one graph edge of the load…so it just makes it much simpler.

tony.kay19:08:46

same with load markers, etc.

tony.kay19:08:09

load-data just couldn’t be made to “do the right thing” in many many cases

tony.kay19:08:07

I’m glad to see that your initial loads mapped over so cleanly. I was hoping that was the kind of thing people were doing with load-data.

tony.kay19:08:31

instead of nested manual graphs with nested parameters on query props 😜

currentoor20:08:34

yeah that sounds like a headache

gardnervickers21:08:01

The network layer is joining queries if they don’t have the same root key? I was under the impression that

[{[:person/by-id <>] [:person/id :person/name]}
  {[:person/by-id <>] [:person/id :person/age]}
Would result in [{[:person/by-id <>] [:person/id :person/name :person/age]}] Is that not the case?

tony.kay21:08:02

Oh, not sure it’ll do that..you mean for the same ID?

tony.kay21:08:26

😕 My memory may be faulty

tony.kay21:08:42

I think @jasonjckn did that patch….Jason, do you remember?

gardnervickers21:08:44

Ah yes I guess it would only really make sense if it was for queries rooted at an ident

tony.kay21:08:18

I know it tries to do “the right thing”. Remember the response is a map, so only one key per response is possible.

tony.kay21:08:41

and you could have alt subqueries and/or params

gardnervickers21:08:03

Ah hm I might have been confused seeing this in vanilla Om.next, but now I’m interested if Fulcro does anything like this?https://github.com/omcljs/om/blob/master/src/test/om/next/tests.cljc#L1805

gardnervickers21:08:22

Besides what you mentioned above, on non-ident keys.

tony.kay21:08:55

I wrote that test 😉

tony.kay21:08:09

that is part of process roots

tony.kay21:08:25

which we don’t use/need

tony.kay21:08:43

it’s part of what motivated me to make Untangled, in fact 😄

tony.kay21:08:53

process-roots seemed such a nightmare

tony.kay21:08:35

as process-roots eliminates “roots”, you can end up with queries that collide on the outgoing side

tony.kay21:08:59

so, it is a similar problem

gardnervickers21:08:00

So for the routing example, you could re-root your query at the level of each page?

tony.kay21:08:11

you mean in Om Next?

gardnervickers21:08:27

Yea, if using a union routing solution like Fulcro has

gardnervickers21:08:48

Versus Fulcro having you do a data load on the component?

tony.kay21:08:10

well, in general, in Om Next, the remote queries always end up going to the netowkring with path all the way from root…so process-roots lets you strip off (and re-add) the stuff the server doesn’t want to see.

tony.kay21:08:16

like the UI routing and screen crap

tony.kay21:08:33

in Fulcro, you write the query from the component that is actually persistent…so there is nothing to strip

tony.kay21:08:17

Om Next: ui-rooted == process-roots ==> server query ==> server-response ==> process-roots ==> ui-response ==> merge

tony.kay21:08:31

Fulcro: query => server => response => merge (optional move when using :target, or post-mutation to alter UI tree based on result)

gardnervickers21:08:18

Ah thank you that helps my understanding quite a bit.

gardnervickers21:08:38

Ahh yea the post mutations and use of :target end up replacing process-roots response rewriting then?

tony.kay21:08:07

Not to mention that in Om Next you have to write a parser emitter to actually even get the remote query. It is just peppered everywhere: parser logic, process roots in network logic, helpers in merge logic….

tony.kay21:08:04

Yes. The targeting lets you put the result where you need it…but since things are normalized, that isn’t so bad (3-levels deep, max)

tony.kay21:08:15

whereas with process-roots, it was against the UI tree!

tony.kay21:08:43

(targeting is post-norm/merge…process roots is pre-norm/merge)

tony.kay21:08:12

it’s one more advantage to picking the db format. In Om Next (pre-decisions on various configurable things) you can’t do it. The pluggable database keeps you from pre-writing the logic.

gardnervickers21:08:14

Ok that made it click for me, forgot about the restrictions not knowing the DB format imposes.

tony.kay21:08:58

well, the parser is also a big wrench in the cogs…even with a db format, there’s nothing to say how you convert to-from the db because the parser is unknown logic

tony.kay21:08:54

The concepts are all great (which is why I’m basing off of it), but wow is it somewhat impractical to build a real app on top of without a lot of base work that has little to do with your app

gardnervickers21:08:31

Yea the parser is insanely hard to write without some well thought out conventions about recursive db access, although we’ve been slowly locking down those patterns and using Fulcro’s local reads to implement keys representing a projection of app state. Some of our computations were difficult to keep up to date with mutations and much better suited as derived views.

gardnervickers21:08:00

Having :read-local has been a pretty great escape hatch for us.

tony.kay21:08:01

@gardnervickers oh,you mean the new support for hacking into the parser???

tony.kay21:08:12

Nice! I’m glad to hear someone is using it.

tony.kay21:08:36

How’s performance when recomputing those every UI refresh? Or are you also doing some other kind of caching?

gardnervickers22:08:22

We actually need to recompute them every keystroke, but we’re running them either in a webworker or in a core.async go-loop so they kinda naturally end up cached in a channel for us.

tony.kay22:08:08

oh, I see…let another CPU grind on them. Smart. That’s a super-cool use-case.

tony.kay23:08:06

Yea! David and I worked through two bugs with code-splitting. The cljs compiler at the tip of master works now 🙂 So, dynamic (auto-loading code-split screens) router and dynamically loading i18n locales will soon be solid features!

tony.kay23:08:43

Clojurescript 1.9.905 and above