This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2015-11-11
Channels
- # admin-announcements (71)
- # beginners (8)
- # boot (109)
- # cbus (5)
- # cider (27)
- # cljsrn (77)
- # clojure (65)
- # clojure-austin (5)
- # clojure-berlin (1)
- # clojure-brasil (1)
- # clojure-dev (58)
- # clojure-japan (15)
- # clojure-russia (193)
- # clojure-seattle (3)
- # clojurescript (120)
- # cursive (19)
- # data-science (1)
- # datomic (10)
- # docs (1)
- # editors-rus (17)
- # emacs (2)
- # events (1)
- # funcool (7)
- # hoplon (2)
- # jobs (1)
- # jobs-rus (16)
- # ldnclj (7)
- # leiningen (3)
- # off-topic (12)
- # om (450)
- # onyx (122)
- # re-frame (69)
- # reagent (28)
- # yada (20)
The only enhancement I currently wish for is what we briefly discussed last night (something like txbind
- possibly in a completely different form - either baked into transact!
or as a separate function to resolve transaction reads to component subqueries before they are passed to transact!
). Everything else just works so far. I'm really liking it.
@dnolen: here’s the example I mentioned of a recursive parser and using params as state. I definitely don’t know if this is a good direction to go in but I hope it at least demonstrates the idea a little bit https://www.refheap.com/111552
@noonian: yeah there several ways to solve that I don’t know about using params so heavily for this
I think it’s important to keep data concerns and view concerns somewhat distinct for cases like this
Yeah I’d love to see an example of how to use subquery. It wasn’t obvious to me from the docs.
which is to stick everything into the root query statically, but then you have the motivation for delayed queries
yep, thats spot on with what I was trying to do. I was using a router also but didn’t include that in the refheap snippet
So, after playing with it some more, it occurs to me that I'm going to end up with a remote query that is structured from root, even for the remote stuff. This seems necessary for it to know where the result of the remote query's result is going, E.g. if I have [{:panel [:status {:list-of-server-things [:a :b :c]}]}]
I'm going to end up seeing [{:panel [{:list-of-server-things [:a :b :c]}]}]
, right?
which means I have to write send to "strip out" the UI bits at the top if I want a reusable thing on the server (cause the mobile might not have the nested panel)
e.g. I'm going to send the query fragment [{:list-of-server-things [:a :b :c]}]
over the wire, then plug the result back into the UI-based structure before passing it to the callback
If I'm understanding that correctly, then I'm wondering where we ended up on the path optimization story. I don't see read-ident
on alpha19. If we do the path optimization, then can we make it so components with an Ident always use the alternate read, and therefore won't need this extra processing?
I think I've confused myself. First question: 1. Does a parse with a target need to return a result that structures to root?
E.g. in your remote sync tutorial, the dynamic query includes all of the components all the way to the root
I am assuming this is required, else it does not know how to associate the query/result with the UI components
I was walking through the steps to deal with a remote when the query ends up having UI-specific bits "above" the real server query. Seems like they are:
1. Write the read function to leave only those bits that have to do with the remote.
2. If (1) has to be structured to root, the resulting query will have UI-specific elements wrapped around the server-interesting bits. Strip that bit off before sending it over the wire. parse -> [{:ui-bit [{:server-bit [:a]}]}] -> xfer over wire [{:server-bit [:a]}] -> server
3. On response, plug the server value back into a structure that includes those UI-specific bits of structure and give that to the callback
@dnolen: What? If I have a mobile UI that has one kind of top-level structure, and a desktop app that has a different one, and neither of them have persisted data at the root, then I don't want the UI specific query going over the wire
I know that, but if I have some container that uses only local client state, but composes in something that needs something from the server, then the query will have that client-specific route in the overall query, won't it?
when I say "UI specific", I mean that I've put some data in app local state that is for controlling things like a modal pop up...what if that modal needs to compose in some query for a list of surveys, for example. The "list of surveys" is a query rooted at the user of the application.
from root...at some point I end up "bottoming out", but I have all these local-only paths in the way
i.e. it doesn’t care about the keys actually it just knows the real stuff is underneath
so, I'm not seeing how to write this routing logic you speak of. I assume it is client-side. But, Om runs the parser from root on :remote
. The result of that is a query with the UI structure, right?
you’ll get some top level keys you don’t care about but you know the next level you do
In falcor, you hook a model to a data source, and end up with something you can query directly
my misunderstanding could be the answer to this question: Does the result of Om's call to Parse on a remote have to follow the UI structure?
So, I was saying that my send function gets this "remote from root" query, and since I don't want to write a bunch of different routes on the server for each possible UI bit, I'd want my send to do the "remove local path" part...but you're saying you'd rather see the server just kind of elide the bits as if going through redirect logic
right I have a tendency to want to think through on how we could solve without changing anything at all
So, that was why I mentioned path optimization earlier. With the read-ident idea, we could use that as a way to handle many of the cases...assuming the query for such a component always tried read-ident first.
but the query fragments are different all over the place for other components with the same ident
Yeah, I see the problem...could we pass some marker (e.g. component path) into send so it could return that with the response?
in send we can as you suggest construct the actual roots in to a single query to the server
@dnolen: I like the idea. So, the assumption (a fair one) is that no nested component will specify “I’m a root query” if a parent had already…we’re assuming the “bottoming out” happens once. So, sibling-like things could all root themselves.
I was trying to think about the case where a query AST says “I’m root”, but some sub-portion of that query wants to “change root”. Can the marker be nested?
I just am not familiar enough with the AST to know if this marker could be recursive
@tony.kay: but your idea is a good one I admit wholeheartedly. It gets us a nice Falcor feature w/o giving up on the synchronous model.
thanks, I very much appreciate the amount of sync stuff you’ve managed to pull off. Only one place in the entire app needs to ever see an async call.
yeah, I’m starting to feel better about that part as well. I really didn’t like the idea of having separate declarative queries, but I was also disliking the local complected with the remote query. This is making it more palatable.
Hmm. I have refs in my app state (normalized and all) but if a query for e.g. [{:person/list [:db/id :person/name]}]
comes back from the remote and I have local props set on them (e.g. {:person/list {1 {:db/id 1 :person/name "John" :app/expanded true}}}
), the local props are dropped. Is this a bug or am I supposed to overwrite one of the merge functions for this to work?
What comes back has the shape {:person/list {1 {:db/id 1 :person/name "John"}}}
.
Something that resembles (merge-with #(merge-with merge %1 %2) {:person/list ...local...} {:person/list ...remote...})
should, at least in this particular case, do the job of preserving local props.
@tony.kay: https://github.com/omcljs/om/blob/master/src/test/om/next/tests.cljs#L674-L685
so I’m pretty ok with how recursive remote query construction works, it’s really more or less like parsing but on the AST instead of the actual query
Would a complete recursive walk, merging maps at all levels, make sense or is that undesirable?
One problem with it is that properties that disappear on the server side (e.g. :person/subscribed?
or something) would never be deleted if all maps returned by sends are merged. A different idea could be to mark parts of the ast with hints like "preserve [:app/expanded ...] here" and have merges honour those hints.
Then again... a prop disappearing would easily break the query contract set by components.
Mhm. Doesn't break contracts and is different from empty values (`""`, []
, {}
...). I'm fine with that.
Have to ask, to make sure. If i have a component that needs to get data from two root branches, my options are: 1. data from one branch goes to the root and is a :shared value 2. data from one branch goes to the root and is passed down via om/computed 3. a custom parser
i would say that 1 would be good for something like shared resources (i18n, ...) or other data that would normally be placed at the root.
@thomasdeutsch: also if I was using DataScript I would might just add a relationship in this case
yes, right. But if i have a dynamic list? then i am back at those two options i think.
@thomasdeutsch: I don’t follow
then, what do you mean by adding a "relationship"
i thought that you would suggest me to add refs to my entities ( but that would exclude dynamic data )
@thomasdeutsch: why can’t refs point to dynamic data?
right. but i nave no entity that contains a list of items - i have to find the items.
@thomasdeutsch: ah mean you need to run a query for this thing
@thomasdeutsch: wasn’t clear to me that was a requirement
when running a local state transition with om/update-state!
in om next componentDidUpdate
is not being triggered, though (.forceUpdate component)
is called internally. Why is that?
@denik: would probably require some experimentation and some cross verification with some simple plain React 0.14 code
@dnolen turns out it’s not being called b/c the component is found in the reconciler:
(if-let [r (get-reconciler component)]
(do
(p/queue! r [component])
(schedule-render! r))
(.forceUpdate component))
but it’s not clear to me that it is - you’ll see we check if the component should update
you haven’t said that your thing doesn’t re-render just that the life-cycle bit doesn’t get called
@dnolen: in conclusion, if i have a component that needs to query some data, i need a custom parser?
@thomasdeutsch: that’s always the case
yes.. ok.. i need a parser that is not only a (pull selector)
@dnolen I think I found the issue: in reconcile!
get-computed
expects a component but is passed props https://github.com/omcljs/om/blob/a539a49979c878c47e2784202b11075093a74301/src/main/om/next.cljs#L1270
@dnolen: so the only way to change an IQueryParams is through a side effect? Just verifying.
@dnolen component?
throws because get-computed
passes it (props c)
which is nil
. It tries to read om$isComponent
from nil.
however changing (get-computed (props c))
in reconciler!
to (get-computed c)
also resolves it for this particular case
@dnolen: Just walked through the query root stuff with the team. It actually works out that the composition falls out without needing direct sub-roots support. Say you have a list of items that comes from the server: (rest-style URI /items). The new query root lets you remote that over. Then later a user clicks on an item (rest-style URI /items/42) and you want to show that as a modal over the list (as a UI sub-component). You already have the items list in memory (app-state caching), so the parser won't even make a remote query for the items again, and the parser can just mark the specific item as the query root for that interaction, even though it is nested in the UI tree So, something that you would analyze as a static UI tree (UI path rest-style: /dashboard/items/edit-dialog/42) looks like it might want multiple roots, but the trick is you don't need them at the same time!
I have not found a use-case (yet) that doesn't fall into this category, because if you think about "sub-roots" they always need the context of an upper root, which must have been resolved (due to sync model) at an earlier time.
:selector
is what you actually use, :rewrite
you use to recover the response expected by your UI
so now that things are shaping up I think it’s time to really fixup some naming issues
this means there will be some churn but it should be search & replace issue for everyone
so AST will get documetation and :dkey
will become :dispatch-key
, stuff that humans can read and not want to break their computer
if there are other naming conventions that people do not like, speak now (or soon) or forever hold your peace
can anyone tell me how to use time-travel with Om next? I can figure out how to get past state with (om/from-history reconciler #uuid "20cd52ee-7ea3-4178-b44a-aba6c4b69a9a")
, but I'm not sure how to overwrite the current state with what this returns
@dnolen: I am about to try temp ids. What's the format for returning tempids -> real ids mappings though? And how would a server-side parser return the mapping?
Yep, I was guessing as much from the tests and the reconciler sources. Just didn't know where :tempids
would come from.
feedback welcome https://github.com/omcljs/om/blob/master/src/main/om/next/impl/parser.cljc#L2-L37
so in read implementations for a join expression the env will contain a :query
key with the QueryRoot that is the value of the join? Instead of the old :selector
?
Quick question about tempids: what's the best way to send them over to the server-side parser? Seems like transit doesn't handle them out of the box. So I I'm thinking add read/write handlers for them to my transit code?
Ok, client side works: (cljs-ajax.core/POST {:params tx :writer (om.transit/writer) :reader (om.transit/reader) ...})
- done
On the server side, (wrap-transit-json-params :encoder (om.next.server/reader))
(similar for response) should work. I'll test in a few minutes. -> Actually, that doesn't work.
@dnolen: alpha20 depends on a snapshot version of figwheel-sidecar. lein doesn't building/installing that unless you LEIN_SNAPSHOTS_IN_RELEASE=true
. It installs fine if you do though
yeah, can’t find it on clojars or maven either so I suspect that issue prevented you from pushing it
but I wanted this release to have a smaller number of variables due to the name changes
@dnolen: migrate breaks either normalization or component updates. Haven't dug deeper yet but will do now
It receives the same results from the server as before but doesn't update anything after the send is merged.
@jannis it may be worth just take a quick glance at the tests at the bottom of om.next.tests
granted there aren’t too many around migrate but it may give you hint where I’m going wrong
@dnolen are you storing the mutation [key params] in the cache or just printing them out? looks like you are just printing them
@bhauman: yeah the Om Next specific integration bits might be better left for Om Next specific cards
@dnolen: totally, was just thinking that if I had a generic history manager protocol that would allow reuse of the current history manager
displaying a transaction dropdown, in addition to the current transaction allong with the manipulation controls seems kinda like the next step
@dnolen: I'm not sure I understand it all and I may be wrong but it might go wrong because (om/get-query (:root state))
is nil
.
om.dom/render-to-str
throws ReactDOMServer is not defined
. is that a bug here or should i look upstream in cljsjs.react/cljsjs.react.dom?
@dnolen: Yeah... could it be that :root
is set on (:state reconciler)
, not (:state (:config reconciler))
? merge!
assumes it's set on the latter.
@dnolen: Might be an accident, as there is a local state
variable overriding the state
member of the reconciler in merge!
.
@joshfrench: upstream
@dnolen: Doesn't the om.transit/reader
handler function need to be wrapped in (read-handler (fn [id] ...))
?
I get this error when trying to parse tempids on the server side: java.lang.ClassCastException: om.transit$reader$fn__5476 cannot be cast to com.cognitect.transit.ReadHandler
I assume it's because the :handlers
opt needs to be a map of read handlers with a fromRep
function.
FYI, the new query root stuff seems to work as advertised. Just got the first one running, and it did what I expected.
@squeedee: Make sure you handle error cases and such. If your server returns an error, for example, and you just re-ask the same question...well..that is your loop
Your read functions need to change to a local state request after the request is fufilled
e.g. Once it is in app-state, you should return it with :value...use an if (if not-in-app-state {:remote ...} {:value ...})
Using both in the same return value is saying "I have a value here, but I'd also like to update it using the server"
well, you have to make sure your read knows how to detect when to ask the server, and when to ask local state
my understanding is remote will only be used in case of transact! and for initial load
remote will be hit any time your read functions return stuff that says a remote needs to be used. Any re-render could trigger that (e.g. set-params! could need a remote to run)
transact allows follow-on reads to be specified, so that you can ask for updates to data that a transact might have changed
I dont understand how im supposed to read fresh data from a remote after transaction then
Depends. It definitely must read the state from local state during the normal read loop
If your processing of the transact went remote, then your remote already responded with the data
in that case, only a local read from state is needed. If you say remote again, you'll loop
but the :item-to-read will only be included in the remote call according to the logic of parse
My apologies...I have to step out...I'm giving a talk in a few mins I think @jannis can explain it
Caching in the local state + :remote true
works fine. If you add :item-to-read
to the transact!
call, the remote will be run again. It shouldn't loop though.
No, if you LOCAL read always returns {:remote ... :value ...} then you will loop forever
code snippet will help, but I've run into the same thing. I'm willing to put money on your read returning a remote when it should not
so my understanding is that you need to control the semantics you want wrt when to query the server by your read functions
by returning a value and saying remote you are returning a value (optimistic state) and sending the query to the server every time
so if you wanted to only query the server if the data wasn’t already on the client you would only return :remote true when its not there
I don’t know if om has anything built in, but you could definitely have your mutations set a flag in the client state and check that during your reads to determine when to remote
or if your server is changing it would need a websocket connection to tell the client when to re-read or possibly just send a result over the connection
@dnolen: Mmh, I like this tempid stuff a lot. I had a similar idea years ago after writing http://git.xfce.org/xfce/tumbler/ (a thumbnail generation service for desktop applications) and realizing what a pain it was to keep track of large amounts of queued thumbnail requests in clients if they need to wait for the service to return them IDs for those requests asynchronously (they'd have to keep track of several levels of temporary IDs, IPC call handles, request IDs generated on the service side, and, lastly, the files they requested thumbnails for).
I thought: why not let clients pass their temporary IDs to the service, perhaps prefix them using the process ID (making them unique) and only ever use those to identify thumbnail requests - on both sides. Never got around to doing it that way but I'm glad to see you avoiding that problem from the start with tempids and migrate. Very cool.
@dnolen: I noticed the doc example for om.dom/node still has the code (om.next/dom-node some-component)
, can I go ahead and update that to (dom/node some-component)
? I know some of the pages ask people not to make edits, but this one doesn’t seem to
I am referring to the Documentation (om.next) page of the wiki
the upgrade from alpha5 to alpha20 was not too bad