This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2016-05-31
Channels
- # admin-announcements (4)
- # alda (3)
- # aws (1)
- # beginners (2)
- # boot (33)
- # braid-chat (4)
- # braveandtrue (20)
- # cider (52)
- # cljs-dev (13)
- # cljsrn (55)
- # clojure (111)
- # clojure-belgium (4)
- # clojure-brasil (6)
- # clojure-dusseldorf (1)
- # clojure-greece (116)
- # clojure-mexico (1)
- # clojure-nl (3)
- # clojure-russia (56)
- # clojure-spec (72)
- # clojure-uk (13)
- # clojurescript (66)
- # community-development (2)
- # component (24)
- # core-async (1)
- # cursive (19)
- # datomic (27)
- # devcards (5)
- # emacs (1)
- # funcool (34)
- # hoplon (313)
- # jobs (1)
- # lein-figwheel (11)
- # luminus (5)
- # mount (30)
- # off-topic (63)
- # om (375)
- # onyx (67)
- # perun (8)
- # proton (1)
- # reagent (4)
- # rum (1)
- # specter (55)
- # spirituality-ethics (7)
- # test-check (2)
- # untangled (34)
- # yada (20)
Are there any good examples how to handle normalization for tree-like data, to be rendered with recursive components?
@bendlas: Anmonteiro has two blogposts: https://anmonteiro.com/2016/01/exploration-patterns-om-next-part-1/
@iwankaramazow: thanks
In this test: https://github.com/omcljs/om/blob/master/src/test/om/next/tests.cljs#L631
how does the query know to only recur up one level (to :node-value
) and not to :tree
?
also, how is all of this implemented? grepping for ...
, i can only find it in test files
oh, found the latter one https://github.com/omcljs/om/blob/master/src/main/om/next/impl/parser.cljc#L99
Has anyone got any kind of batching mechanism running in his/her backend parser?
@anmonteiro: dataflow / interactions between normalization / query and the parser / parser env.
@anmonteiro: The purpose of dynamic queries: set-query!
with IQueryParams. My working understanding is that they are not required, as any state can be kept in app state, and reacted to in the component, including things such as sort order etc - the things you are supposed to use set-query!
for.
... so normalization runs just based on static queries, right? so what to do, when the queries are based on data transformed by a parser?
@bendlas: keep in mind that queries can evolve over time
set-query!
or IQueryParams
@hlolli: that's just a naming-convention
It is idiomatic to use namespaces to group attributes that typically appear together. This is introduced by Datomic.
Much clearer imo, when your app grows
let's say you have a current logged in user that has a :name
and a list that shows the name of artists
:user/name
& :artist/name
less confusion
yes, could just as well be :user-name
and :artist-name
. I say this bit foolishly because I know the slash can give some functionality to the keyword. Once which I don't understand at all; that you can destructure the later part of the keyword in let [{:keys [name]} (om/props this)]
for detmethod read :artist/name
, at least that's the case for remote-synchronization tutorial where results
from :search/results
is destructured like this.
As for multimethods, I would assume just any symbol, string or keyword would be valid dispatch, getting confused by the requirement to give a :ns (or category) infront of keyword name.
No I made one wrong assumption, it actually looks like this (c/p): let [{:keys [search/results]} (om/props this)
and results becomes a valid symbol without search/ infront of it.
(NB. like always, what one can oversee is ackwardly obvious sometimes. Just figured out my problem, which was to key for the given remote key in read).
@anmonteiro: I'm just playing with your recursive example here https://gist.github.com/anmonteiro/2b282aa35380558a8b1d#file-composite-cljs and unfortunately, it breaks down as soon as transactions are introduced
@anmonteiro: here is my version: https://gist.github.com/bendlas/1a16b216fb0fbe0e02ef446711f18c4a#file-composite-cljs when clicking on the rectangles, an unrelated state key is updated as expected, but the elements vanish completely during the rerender
Is there a recommended upgrade path to Om Next at this point? Is there a way to use Om Previous and Om Next in the same app?
I mean, I know they should work as just React components, but is there a trick to getting the data to flow to them correctly?
I'd love to just make the new page I'm building in Om Next, and then gradually spread it through the whole app
@peeja: in theory, you should be able to use the same underlying atom with a reconciler simultanously
and then do I have to use om.next
for every component that's an ancestor of the component I want to use Om Next for?
but I'm not that deep into om.next, and based on current experiences, I think I'm on my way out
it expects to be used in a very specific way, with normalization vis a vis update semantics
the parser functions promise flexibility in the underlying data format, but that flexibility doesn't realize with normalization
I've had a very pleasant surprise with datascript, so I might just use naked react over that, or maybe reagent
I've had plenty experience with om and it works ok, but cursors have always felt clunky
Is your app purely client-side? The major strength I see in Om Next's approach is in moving the remote data fetching closer to the data store.
well, that's the pleasant surprise I've had with datascript: so far nothing beats piping the transaction log into a datascript instance
@peeja: FWIW:
(defui Child
static om.next/IQuery
(query [this]
[:dummy/key])
Object
(render [this]
(let [{:keys [dummy/key]} (om.next/props this)]
(dom/div nil (str "dummy key: " key)))))
(defn parent [state owner]
(reify om.core/IRender
(render [this]
(dom/div nil "parent"
((om.next/factory Child) {:dummy/key "Test"})))))
(om.core parent {} {:target (js/document.getElementById "app")})
you can’t expect the dataflow to go through the parser
but you can definitely start moving your components to defui
Yeah, I figured I could do that much at least. It's the parser I'm trying to work out… 😕
also, the IQuery
implementation is just a placeholder until the next round of refactoring
that’s probably an option too
not sure about the cursor stuff though
but it probably works out
since cursors have map semantics, I think
I'm pretty sure our app only swap!
s the atom. Which is not great in an Om Now world, but actually maps pretty well to the Om Next world.
it only shows transact!
for the instrumentation bit
(I’m assuming that’s the app you’re talking about)
happy to help
@anmonteiro: any idea why recursive queries break with transactions?
looking into it
@bendlas: how should I run your gist?
I mean, which one is it, the composite?
I mean composite vs decorator
which one
gotcha
@anmonteiro: curiously, when triggering the transact!
from outside, nothing happens, so my guess is that rerendering based on the query from the triggering component is broken. Also, force-root-render!
doesn't restore the components when they're gone, so I'm not really sure what's that supposed to do ...
@bendlas: this might just not be a bug in Om Next, but in my example
the problem is really just the biggest square
if you stopPropagation
of the click event almost everything works
(except the big square)
@anmonteiro: not sure about that. I'm trying to port a tree of folders and files to om.next and the bug already manifested before i had a parser for the tree nodes ...
@anmonteiro: but ack insofar, as i see the bug only on clicks on the outermost node
@bendlas: I don’t really have a lot of time but my suspicion is that the problem is related to full-query
whenever you do a transaction, the query that the parser receives might not be the exact same as the initial query
but instead it receives the full-query
for the component, and you might need to handle that case differently in the parser
https://www.refheap.com/119799
I added an asynchronous http/get request in a send function of the reconciler, in a hope to be able to target html(this case svg) elements within componentDidMount
. Still encounter the same problem as if this was not within :send
, browser just returns app.cljs:97Uncaught TypeError: Cannot read property 'addEventListener' of null
but adding (.addEventListener (gdom/getElement "node4") "mouseover" #(js/alert "hello númer 4"))
to the repl, works fine. (sorry to distract the conversation currently active).
(if you wanna pursue the matter further)
@anmonteiro: well, it's either that or ditch om.next entirely .. but the project is already late, so I guess I might be better off using something else 😕
@bendlas: for recursive queries you might also be interested in path optimization
example here: https://github.com/omcljs/om/blob/master/src/devcards/om/devcards/core.cljs#L322-L404
@anmonteiro: is that an actual optimization, or for getting it working?
important bits are: - L331-L333 - L396
@peeja: what i'm doing to have both om.next and om.now is to do it on a per route basis. So I keep track of which version of om is mounted, then unmount/mount according to which route i'm loading
@anmonteiro: ok, thanks. don't hesitate to ping me if you find out more about that click interaction
I’m not going to spend more time on it now
I’ll see if I have a bit later
Thank you so much for your little rerouting example @iwankaramazow this makes me understand a lot more things
@mitchelkuijpers: np, I'm glad to help.
Much appreciated
anmonteiro: catching up on that bug. your intuition was pretty good. it seems that full-query omits the join query for some reason. so instead of {:composite/item {:composite [:id :width ...]}}
, it returns {:composite/item [:id :width ...]}
erm, mention @anmonteiro
@bendlas: that’s exactly what I was talking about
also not a bug in path
path
is about the data path, right? in a union all items are under the same vector, so they have the same data path
once the union branch has been decided upon, that key is not included in the path
you're right, i just tried removing :om-path
from the metadata, but full-query still returns the wrong query
@bendlas: well, probably your parsing code expects a query in the initial form but it doesn’t get that once you perform a transaction and full-query
is fed
the bug is also not in full-query
AFAICT, there’s nothing wrong in Om Next code
you simple need to handle both cases differently
(probably can be solved with path optimization) — hence my suggestion that you look at this
yes, path opt looks like it would solve this, but shouldn't this be just an optimization?
you can also solve it without pathopt
look, in `(first (get-in @(-> component get-reconciler get-indexer) [:class-path->query (class-path component)]))` the argument is a hash-set
just need to account that your read method is going to get 2 types of queries
one that’s a map, other that’s not
no, i get the full-query in the parser, but in the rerender case, it's missing the intermediate join
can you paste the 2 different queries here?
so I can see what’s missing
no, you're right READ :tree/root nil (:query-root :path :pathopt :ast :state :parser :logger :shared :target :query) {:query-root :om.next/root, :path [], :pathopt false, :target :remote, :query {:file [:path {:prop [*]}], :folder [:path {:prop [*]} {:children ...}]}} vs READ :tree/root nil (:query-root :path :pathopt :ast :state :parser :logger :shared :target :query) {:query-root :om.next/root, :path [], :pathopt false, :target nil, :query [:path {:prop [*]} {:children ...}]} in the rerender case
so there’s nothing missing
so, ok, I get that I can fix it in the parser, but this should be handled differently by om.next, right?
this is an optimization already
so that the parser receives the query focused along the path that initiated the transaction
Fwiw I think I ran into this over the weekend as well but haven’t had time to dig in. I do think there is a bug somewhere in Om though. My usecase was similar to the composite recursive union blog post and my component was getting empty props from Om (not passed in from parent) during re-render after a transaction.
@noonian: sounds like this, and if a query parser is supposed to handle QueryRoot
as well as JoinExpr
, like IQuery/query
does, then it's really not an om.next bug, but in this case it really needs to be clarified
@bendlas: you’re conflating two things, this is definitely a valid query grammar
what is happening is that following a mutation, you don’t need to re-render the whole component hierarchy
but instead, only the sub-tree that changed. the root of such sub-tree is the component that initiated the transaction
full-query
is meant to be the initial query, focused along the path of that component
and the new props are fed directly to that component, and not from the root
examples of focused queries: https://github.com/omcljs/om/blob/master/src/test/om/next/tests.cljs#L90
yeah, ok, but grammar-wise what is the :query
key in the env supposed to be, and if it's not strictly a QueryRoot
, then is it not at least supposed to be somewhat "stable"
might be that I'm conflating, if your measure is the current implementation of om.next, but I've now sunk multiple days into getting a grasp of this thing and it's not getting easier ..
@bendlas: so the parser dispatches on the top-level keys of your query
if your root query is [:foo {:bar [:quux]}]
then the parser will run multimethods for :foo
, with query set to nil
, and :bar
with query set to [:quux]
no one ever said that Om Next was easy to learn, and it's definitely not
and you might need more than a few days to actually understand it. especially given that there is just not sufficient enough documentation
Making it easier to learn is definitely a priority, but one that takes time
in this case, I'm thinking maybe the :query
input for parser functions should stick to QueryRoot
and in the case of join queries, there could be an extra :env
key
but rewind a step, according to current impl, what is IQuery/query
supposed to return (`QueryRoot | QueryJoin` or QueryRoot | QueryExpr
) and what are parser functions supposed to handle in their :query
env key?
@anmonteiro: oh, so IQuery/query
can return QueryRoot | UnionExpr
and the parser has to handle accordingly, is that right?
@anmonteiro: i suppose the grammar would also allow for a RecurExpr
to land in the parser, but I'd be pretty baffled if that worked right now ...
all that is under the assumption that the :query
key in the parser is always the RHS of a JointExpr
, if that's the source, otherwise nil
is that true?
@anmonteiro: so, I tried now to handle a map query aswell as a vectory query. To no avail. The failure is slightly different though: now the component doesn't get rerendered with an empty map, but with a map wrapped by the root key.
slightly pointed question: is anybody besides you and @dnolen able to hold this thing in their head and take charge for it?
@dnolen: right now, I'm trying to use recursive components to render a tree of files and folders
I mean, I'd like to skip :pathopt
because i understand it even less than the rest of the parser infrastructure, but if I have to rely on an undocumented hack to get something working, so be it ...
@dnolen: https://gist.github.com/bendlas/6a94f7b5d4d9e3fd30bcda5bc96b2e68 when clicking a node, its data disappears because the rerender fails.
anmonteiro advised me to the condition at L120 where the query passed to the parser is already focussed in the rerender case
but with that condition, the rerender is wrapped by a root key, also the data goes into the component denormalized (other than in the initial render)
@bendlas: if your query is not a map you might just want to call db->tree
. I’m hopeful that would solve the issue
I believe I’m encountering the same issue with db->tree
and no recursive parse. I’m at work atm though and cannot dig deeper right now, but I don’t think it will be too hard to come up with a failing test.
@dnolen: that's lifted from the blog, without it normalized data would slip into sub components, maybe that can be solved with a db->tree aswell
@dnolen: sure, I'll try to reduce it further to what I'd originally have expected. Right now it follows the pattern from anmonteiro's blog
the whole multimethod thing is just a convention that works simply enough with the helpers we provide
sure, I started with trying to map my own data format onto the app structure via the parser, but that didn't really work out, as soon as I needed to do cross - component updates
I mean, it could have worked if I took the hit of adding read keys to transacts!
but what's the point of that
and if you don’t follow the rules then you’re on your own with whatever complications it brings
like any read key added by a component could just as well be in its query expression, encapsulation-wise
I’m more than happy to listen what you were struggling with in the recursive case here
basically my tree was {:props {..} "A" {:props {..} "AA" {..} "AB" {..}} "B" {:props {..} "BA" {..}}}
, right? So my idents would be the pathes, that I would need to construct while walking down.
at the same time, my display area would also have an ident of [:path ["A" "AB"]]
or something
anyway, I got it to render, but then ran into pretty much the same re-rendering issues on transacts as now
so I don’t know if there is a bug here in om.next but basically that’s very likely to not be allowed
well, collections do have value semantics if you treat them opaque ... clojure allows them as map keys
@bendlas: I can’t dig into just now, but I’ll take a look later - I don’t think it will be so hard to make your thing work - I’ll jot some notes and hopefully that can clear some things up
when you say transactors have to add appropriate read-keys, why have the mutator abstraction at all, when mutators must know which keys will be modified?
the rationale is entirely about clients being able to control what they re-read rather than servers sending back random stuff
@bendlas: also the component which initiates the transact, automatically re-reads
sure, when thinking in terms of idents, adding the read keys makes a bit more sense, but then the transaction itself should be targeted at idents as well
the bigger idea is just decoupling re-renders from talking about which components need to update
it’s just about the kind of programs I’m interested in writing after having tried many other things
regardless, I'd like to see you write the kind of programs that are for everyone, even if they don't know it yet
@dnolen: I couldn't find any references to the falcor/relay rationale you mentioned, but I've put what I understood from you into the FAQ
I'll do that, the next time i want to slack off. In the meantime, I'll continue to try to find holes in om.next, that you'll actually acknowledge 😉
speaking of :pathopt
, show and tell time - http://selfsamegames.com/p/4/ (needs a webkit browser)
thanks! heh not actually using recursive reads for the outliner but it would have been the same performance because I use idents in transact
Hi, (still) new to CLJ/CLJS/om.next; I’m having trouble finding an example of a read method which uses its params; I’m a bit lost in the woods so I’ll explain in further detail what I’m doing as it may just be the wrong approach
I have my initial app-state which has three main portions, one is structure is a map of attributes and a vector of children. I recursively render out my structure using this, which is working.
When I read a node w/o children, I would like to look for data, so I call a factory “leaf” passing it the current node’s id. I’d like to then use that parent-id to do something like: (parser {:state app-state} ‘[[:data/by-parent-id 3]])
where 3
is, for example, whatever was passed in… as you can see I’m trying and failing to do this just from the REPL
my read function looks like:
(defmethod read :data/by-parent-id
[{:keys [state] :as env} key id]
(let [st @state]
(if-let [[_ k] (find st :data/by-parent-id)]
(if-let [[v] (get k id)]
{:value v}
{:value :not-found}
)
{:value :not-found})))
so if I manually switch id
in the (get k id)
statement to 3
I’ll get my data back, but I can’t seem to use the param I’m trying to pass in
Maybe I’m just struggling with the destructuring syntax...
@whistlerbrk: try using {:id 3}
as the params and destructure for :id
@selfsame: meaning: (parser {:state app-state} '[[:data/by-parent-id {:id 3}]])
and changing the read method to be:
(defmethod read :data/by-parent-id
[{:keys [state] :as env} key {:keys [id]}]
(let [st @state]
(if-let [[_ k] (find st :data/by-parent-id)]
(if-let [[v] (get k id)]
{:value v}
{:value :not-found}
)
{:value :not-found})))
? (sorry very new to the whole ecosystem)@whistlerbrk: can you post your code to refheap. If you specify a read but don't get it in your component, it could be the static query that is missing.
@hlolli: yes, I’m going to make a reduced example
@whistlerbrk: the params arg is for IQueryParams
or queries with the list syntax like (:foo {:params :etc}
@hlolli: @selfsame https://gist.github.com/whistlerbrk/494fc77c76dbe16436b40eeab24f1ba5
@selfsame in my defui
for Leaf
or my read function for that key :data/by-parent-id ?
@selfsame: sorry, I’m not following completely, so are you referring to library functions like om.next/query->ast
?
you have the gist of what I’m trying to do on L90, that is once I hit a leaf, get the data by passing the parent-id
yeah the env
in read will have :ast
, I do see what you're trying to do. I think maybe you can get by without custom read, try (parser {:state app-state} '[{[:data/by-parent-id 3] [*]}])
?
if not, try a pprint on (:ast env)
it should have all the things you'd need to do what you want 🙂
@selfsame correct me if I’m wrong, there is no default read function though, right, I must supply a read function here though
and my current read function is faulty
@selfsame: I could do that, I was trying to comply with the default db
@whistlerbrk: yeah sure - best way to learn, I guess the take away is you were confusing what the params
arg is, and that env
has the info you need to roll your own read methods in :query
and :ast
@selfsame thanks, I’m going to try and find more examples which use om.next/db->tree
I actually haven’t found a ton yet
so this would be valid in my case: (om.next/db->tree [:template/name] app-state reconciler)
“Given a query expression, some data in the default database format, some application state data in the default database format"
meaning [:menu 0]
with {:menu {0 {}}}
in the app state, multiple components can have that ident
it's just, when i have data that hasn't any good natural idents, i'll have to come up with something
synthetic idents from custom reads would mean, that i could key them on their path into the data structure but that seems hairy
but now I just make om.next normalized data and only use a single db->tree
read and everything just works
relying on automated (de)normalization seemed pretty finicky, for the short time I've played with it
I suspect this issue may be what I’m running into and the union and recursive stuff is just making it seem more complex: https://github.com/omcljs/om/issues/604