Fork me on GitHub
#om
<
2016-05-29
>
ag00:05:32

oink… I fixed the first part of the problem

ag00:05:48

now I still can’t figure out how to separate queries taking it out of context of joined query and only update things that are children in joined query

dnolen00:05:16

toying around with cljs.spec and Om Query syntax

ag06:05:59

arrgghh… goddammit… someone please explain this to me, it's driving me nuts. I have a component that has a join query, e.g.:

(defui FooBar 
  static om/IQuery
  (query [this]
        `[{:data/foo ~(om/get-query Foo)}
         {:data/bar ~(om/get-query Bar)}]))
I have read methods for Foo and Bar that dispatch on :data/foo and :data/bar, now I want to have RootComponent that would include FooBar, e.g:
(defui Root
  static om/IQuery
  (query [this]
    `[{:root-data ~(om/get-query FooBar)}]))
now I need a read method that dispaches on :root-data, right? So how do I tell it to find pieces in the state for :data/foo and :data/bar and if something missing ask server for data? Do I have to manually analyze the query?, is there a trick I could use to make this easier? I thought I could just do something like this:
(defmethod read :root-data
  [{:keys [ast query]} _ _]
  ... {:remote (om/query->ast query)}
  )
basically instead of sending whole AST (including :root-data header) only send for foo and bar, and since server already knows how to handle that data, it should just work but it doesn't

ag06:05:51

I just don’t get it - if I have a component that works fine when used independently, but adding it to another component, makes it difficult to fetch data? Clearly I am missing something here

cjmurphy06:05:54

@ag It doesn't need to be so difficult. set-query and fiddling with the AST on the client, in fact even having reads at all - these things are not that necessary for developing simple applications. I'm sure there are useful things you give up when you use a higher level framework such as Untangled. But I haven't minded giving up these three features.

ag08:05:01

I think I need to use (om/process-roots)

ag08:05:21

can someone help me with query-root thing: I believe in my send I did everything right, yet something seems to be wrong with read, can someone check if this looks right, it’s not sending what I want it to:

(defmethod read :root-data
  [{:keys [state ast query]} key params]
  (let [st  @state]
    {:remote (assoc ast :query-root true)}))

(defui SuperRoot
  static om/IQuery
  (query [this]
    [{:root-data (om/get-query LedgerAccountIndexRoot)}])

  Object
  (render [this]
    (html [:div])))

(om/add-root! reconciler SuperRoot (gdom/getElement "app"))

ag09:05:45

according to this SO post http://stackoverflow.com/questions/35675766/om-nexts-query-ast-and-ast-query-functions query-root has to be added to the children components related reads, but that’s the thing - I need to change it from root’s read

ag09:05:17

maybe something wrong with my send?

(defn send [queries cb]
   (let [xhr          (new js/XMLHttpRequest)
         {:keys [query rewrite]} (om/process-roots (:remote queries))
         request-body (transit/write (transit/writer :json) query)]

     (.open xhr "POST" "/ledger-data")
     (.setRequestHeader xhr "Content-Type" "application/transit+json")
     (.setRequestHeader xhr "Accept" "application/transit+json")
     (.addEventListener
       xhr "load"
       (fn [evt]
         (let [response (transit/read (transit/reader :json)
                          (.. evt -currentTarget -responseText))
               restructured-response (rewrite response)]
           (cb restructured-response))))
     (.send xhr request-body)))

ag09:05:11

using process-roots shouldn’t break any existing components, right? now when I add LedgerAccountIndexRoot as a root component it doesn’t work, whereas without process-roots it used to

ag09:05:16

is there anything needs to be done on the server side to accommodate process-roots in send?

iwankaramazow09:05:09

@ag: process-roots is client-side only

iwankaramazow09:05:45

your need to call the parser recursively at :root-data

iwankaramazow09:05:17

only then will the parser dispatch on :data/foo and :data/bar

iwankaramazow09:05:35

do you know what 'calling the parser recursively` means?

ag09:05:57

errr. not sure

iwankaramazow09:05:16

I'll make an example

iwankaramazow09:05:47

(defmethod read :app
  [{:keys [parser query ast target] :as env} key _]
  (let [remote (parser env query target)]
    (if (and target (not-empty remote))
      {:remote (update-in ast [:query] (fn [query] remote))}
      {:value (parser env query)})))

(defmethod read :navbar/items
  [{:keys [query state ast]} key params]
  (let [st @state]
    (if (nil? (get-in st [:app key]))
      {:remote (assoc ast :query-root true) }
      {:value (om/db->tree query (get-in st [:app key]) st)})))
Let's say you have a navbar under the key :navbar/items that currently lives under :app in a join. The parser will dispatch on :app. There you need to go one level deeper (because of the join). To go one level deeper, you call the parser recursively: (parser env query), the parser sits in the env. You just have to pick it out

iwankaramazow09:05:28

the full query in this example is [{:app [{:navbar/items [:path :title]}]}]

iwankaramazow09:05:14

the :query-root true part needs to live in the children you want to make the root of the query

iwankaramazow09:05:50

in your example you mark :root-data with :query-root true, and :root-data already is a root...

ag09:05:10

hold on… I’m still trying to contemplate this

iwankaramazow09:05:24

😄 took me a long time, till I figured this out

iwankaramazow09:05:35

(let [remote (parser env query target)]
    (if (and target (not-empty remote))
This is logic to determine if we're currently targeting remote queries, we have to make sure they aren't empty. The parser runs twice, once for local reads, once for remote reads

iwankaramazow09:05:46

{:remote (update-in ast [:query] (fn [query] remote))}, means we need to modify the ast to pull the query one level up

iwankaramazow09:05:25

try running this example, and printlnevery step

ag09:05:35

ok… I think I’m kinda getting it now… God. I wish these things were documented somewhere

iwankaramazow10:05:34

Yea Om Next needs to be documented like x10. Tony Kay's tutorial does a good job though.

iwankaramazow10:05:48

It's still alpha...

ag10:05:10

I convinced everyone to use Om Next, and for that it keeps stabbing me in my back over and over. I can’t even build simplest things… ;(

iwankaramazow10:05:58

Om Next is a wonderful piece of engineering, you have to master the basics first though

iwankaramazow10:05:04

Skin in the game 😄

iwankaramazow10:05:42

Also if you don't have the problems that Om Next is trying to solve, you might be better off choosing something else

ag10:05:27

yeah, thanks but, no. I’ve been through that. it would seem at first that you won’t have those problems ever, then suddenly you wish you never started that project on angular

iwankaramazow10:05:59

😄 😄 😄

ag10:05:03

thanks man, I’ve been literally banging my head on my monitor and crying

iwankaramazow10:05:49

np, feel free to shoot me questions

ag10:05:22

yeah, definitely. this is gonna be a long, long journey

ag10:05:49

@iwankaramazow: there’s something missing I’m not sure exactly what, it’s fetching data, but not putting it in the right place in state, can you take a look here please: https://github.com/agzam/om-basic-app/blob/master/src/cljs/om_basic_app/core.cljs

ag10:05:29

if I use component directly by swapping commented line, it works fine, but when it is sitting inside SuperRoot it’s not working

anmonteiro10:05:21

@ag: you can either override merge-tree in the reconciler or call the callback in send with a query that you want to use to normalize the response

anmonteiro10:05:48

I haven't looked at the link but that should be what you're struggling with

ag10:05:23

is there an example of how to override merge-tree?

ag10:05:51

I think I found something..

iwankaramazow10:05:58

merge-with into

iwankaramazow10:05:23

it depends on what you're trying to acomplish

ag10:05:26

so I did :merge-tree (fn [a b] (spy (merge a b)) (merge a b)) and it does show that :root-data has indeed the data, but the data does not appear in (om/props this) in render

ag11:05:02

eh, nvmd.. I’ve figure it out

ag11:05:37

@iwankaramazow: thanks to you I may able to sleep tonight… although it’s 4 in the morning here 😉

bendlas18:05:34

The FAQ says, that if a transact! updates an unrelated key, one needs to add it to the transaction. Isn't this a blatant violation of encapsulation? How do people get around this, so that update logic can be contained within the reconciler?

adambrosio19:05:57

@ag: http://untangled-web.github.io/untangled/ & #C0PULSD25 might be worth a look if you want a framework/set-of-libraries to help you use om next it’s made by @tony.kay btw

dgroulx19:05:37

@bendlas: Saw that too, I’m not sure but I think the docs are out of date, because now the mutate function is supposed to return a map with a :value map that reports which keys were touched, thus keeping knowledge of all keys touched by a mutation encapsulated in the same place.

bendlas19:05:59

@dgroulx: I was under the impression, that the :value key, returned by the mutate function, is purely for documentation purposes. I didn't think it would update anything ..

bendlas20:05:51

related, how do I directly combine queries for components, without putting the query of a sub-component into a sub-key? I can't just concat the queries, because then the sub-component isn't on the metadata, which leads to this not being usable for transact!

bendlas20:05:21

my current workaround is to use a sub-key and duplicate the data in the reconciler, but that leads to the problems above, with updating "unrelated" keys

iwankaramazow20:05:22

@bendlas: you can't circumvent this

iwankaramazow20:05:12

About breaking encapsulation: transact! is a subtree concern. A UI cannot possibly indicate what things it depends on at some "leaf" of the UI and hope to remain composable, since that leaf component should care only about its local state. Thus, mutations that can affect many things can only be reasoned about at some node in the UI tree that "owns" all of the things that can be affected.

iwankaramazow20:05:46

The top-level component of that subtree should pass a function down which contains the mutation.

iwankaramazow20:05:52

I don't know if this makes sense though

iwankaramazow20:05:30

a mutate function in the parser doesn't care about return keys though

iwankaramazow20:05:37

those are only for documentation purposes

bendlas20:05:20

@iwankaramazow: doesn't quite make sense. I thought composability was gained by having an interpreter in the reconciler.

bendlas20:05:58

subcomponents should just specify a transaction. everything else will be decided up-tree

bendlas20:05:42

or are you talking about the inability of a containing component to transform transactions from its subcomponents?

iwankaramazow20:05:44

I'm talking about: example (transact! reconciler [(fire-missiles!) :some-unrelated-key])

iwankaramazow20:05:17

it's the :some-unrelated-key I'm talking about

bendlas20:05:22

exactly: that example doesn't make any sense at all, since a leaf should never now about :some-unrelated-key

bendlas20:05:30

yet, it's currently unavoidable

bendlas20:05:57

except, maybe, if you don't use transact! in leaves and instead pass down an event handler

iwankaramazow20:05:58

if you have a mutation deep down in the view, it triggers an update. let's say you add a friend. Your friend-count on some other part of the screen has to increment

iwankaramazow20:05:21

coupling is needed?

iwankaramazow20:05:30

does this really break encapsulation?

bendlas20:05:46

well, the reconciler should know about it and cause all necessary updates

bendlas20:05:14

yet, it can't

iwankaramazow20:05:55

I've no idea if this is possible

iwankaramazow20:05:16

@anmonteiro knows why this probably isn't 😄

bendlas20:05:29

well, I hope that he'll get around to straighten this out for me. it's really frustrating

anmonteiro20:05:35

@bendlas: imagine you have a list of counters in your UI, and you have each counter remove itself from the list. because Om Next indexes which queries map to each components, when you transact! on a single counter to remove it, it doesn’t have any knowledge about the list it belongs to (or possibly, a number of lists). So you need to specify which part of your UI tree needs to react (pun intended) to such change, by specifying a key to re-read

anmonteiro20:05:37

also to clarify, :value {:keys …} in a parser’s mutation doesn’t serve any other purpose than documentation of such mutation

bendlas20:05:51

well, I was just telling @dgroulx about the :value {:keys ..} caveat, so no news there.

bendlas20:05:15

I'm really trying to work out how to use this in practice

anmonteiro20:05:33

in most cases this might not be needed

anmonteiro20:05:53

i.e. if you get transactions right

bendlas20:05:15

so what's the deal? don't transact from subcomponents?

anmonteiro20:05:17

in my example you could perform the transaction on the list (passing the counter ID by param to the mutation) and not need to re-read any key

bendlas20:05:33

no, that doesn't work out currently

bendlas20:05:50

as soon as two sibling components render the same piece of data

bendlas20:05:05

because of inability to horizontally compose queries

anmonteiro20:05:20

so there you go

anmonteiro20:05:26

2 different subtrees

bendlas20:05:42

yep, my point is, it's the same subtree in data

anmonteiro20:05:43

are you using normalization though?

bendlas20:05:50

not currently

anmonteiro20:05:54

so there you go 🙂

anmonteiro20:05:11

if you were using normalization, it would solve itself

anmonteiro20:05:26

because Om Next is smart enough to know which idents map to which components and keep the UI in sync

bendlas20:05:31

might be, but that means I'll have to use idents

anmonteiro20:05:13

but what you said just screams normalization to me

bendlas20:05:16

my use case is just this: I have information about a currently showing modal dialog in my app state

anmonteiro20:05:23

“two sibling components render the same piece of data"

anmonteiro20:05:26

normalization

bendlas20:05:52

as soon as that dialog is enabled, i need to show it as well as darken the background, this are sibling components

bendlas20:05:36

so if normalization is the answer, this needs to be documented in the FAQ, because right now I wasn't able to make that connection on my onw

anmonteiro20:05:50

I can agree that Om Next needs a lot more documentation

anmonteiro21:05:19

feel free to add that to the FAQ, I think the FAE is supposed to be collaborative

bendlas21:05:21

also still, I don't see why I need to make my components smarter for this use case and not just be able to solve it in the reconciler

anmonteiro21:05:33

what do you propose?

bendlas21:05:31

1) a way to compose queries from sibling components without artificial sub keys

bendlas21:05:37

2) the same behavior, whether an update comes from within a transaction or from swap! ing the underlying atom

bendlas21:05:17

are either of those feasible, theoretically?

bendlas21:05:39

also, why can't just mutate functions trigger updates to arbitrary keys?

bendlas21:05:00

@anmonteiro: sorry if you're getting tired of answering this questions for the nth time .... I promise to update the FAQ as soon as I have a clear picture

bendlas21:05:55

and if normalization is the only answer we're giving, I'll document that

petterik21:05:37

I suspect apply is misplaced in om.next/update-query. Shouldn't it be (set-query! component (apply f ...)) instead of current (apply set-query component (f ...)): https://github.com/omcljs/om/blob/master/src/main/om/next.cljs#L743

adamfrey22:05:30

PSA: Remember to add the ^:once metadata to all of your components created with defui if you are using a reload tool like Figwheel or boot-reload. If you don’t you might run into errors like mine: "No queries exist for component path (sample/A sample/B)" I just spent an hour debugging that error. sample/B was missing the ^:once metadata and getting re-evaled on every reload, but the old component instance was cached in the Om indexer.

petterik23:05:29

Filed a bug for misplaced apply in update-query