This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2016-02-28
Channels
- # aatree (1)
- # announcements (1)
- # beginners (18)
- # boot (12)
- # cljsjs (1)
- # cljsrn (6)
- # clojure (358)
- # clojure-japan (1)
- # clojure-russia (69)
- # clojurescript (20)
- # core-async (32)
- # cursive (6)
- # datomic (57)
- # devcards (3)
- # dirac (2)
- # editors (8)
- # emacs (6)
- # juxt (1)
- # ldnclj (4)
- # luminus (2)
- # om (91)
- # om-next (2)
- # onyx (11)
- # reagent (7)
Going through Tony Kay tutorial, as an exercise: Is there standard syntax for JOIN everything? Like with this data:
{:people/by-id {1 {:person/name "Sally" :person/age 33}
2 {:person/name "Jesse" :person/age 43}
3 {:person/name "Bo" :person/age 13}}}
how can I get all people names? I know I can do smth like [{:people/by-id [{1 [:person/name]} {2 [:person/name]} {3 [:person/name]}]}]
, but imagine that I don’t know all id’s? Can I somehow make a JOIN by-id to smth like *
and then join to name
?
Or can I do two queries, one for all-ids and another for names?It’s a silly example, but I’m just trying to fully understand om queries here
you can make a key ‘people’ or ‘all-people’ or whatever, that grabs the vals of ‘people/by-id’ … [{:people [:name]}]
but it isn’t real a query syntax as much as just deciding if you want to expose that data via a key
hm, but then it means that I would have to manage all-people
manually and add/delete items there
sometimes you want that, but if you really want it to be ‘everything in people/by-id’ then you just grab those vals
yeah, you right - if I need those values the right way would be to manage it
thanks!
yeah-yeah, I know this basic staff, thanks
Another question if I may. Getting into Unions queries with DB like:
{:panels [[:panelA 1] [:panelB 1] [:panelC 1]]
:panelA {1 {:boo 42}}
:panelB {1 {:goo 8}}
:panelC {1 {:sticky true}}
:current-panel [:panelA 1]}
both those queries returns the same result:
>[{:panels [:boo :goo :sticky]}]
>[{:panels {:panelC [:sticky], :panelA [:boo], :panelB [:goo]}}]
{:panels [{:boo 42} {:goo 8} {:sticky true}]}
what the point of union then?ah, sorry - got it. With Unions I can specify custom queries for each group, which may be much more complex than simple props
I found a problem. I have a setInterval with some mutation, as it's a setInterval, I set it up on componentWillMount and clear on componentWillUnmount. All this works perfectly. In the mutation I search some data and update a key according to the data I get. All this using Datascript. But after talking here. I found out that this approach isn't good, as it doesn't have any input and it always returns different output, "side effects" as we call it. But if I try to refactor the code in a way that I query in the component level the data I need to use to update some key inside the mutation(so it becomes side-effect free), the component stops updating. The reason seems that when I set the "setInterval" in componentWillMount, the data passed there will always be the data of when the component was mounted. A very easy mental example of this is, let's say you had a counter that increases with time, with a setInterval of 1000 that adds a random value between 1 to 9 there(a side-effect, so it this number to be passed to the mutation), if you build this "setInterval" inside componentWillMount, it will only work for the first second, all the subsequent will be trying to enter the same data. How to circumvent that problem? Is it possible to have my mutation side-effect free in this case?
@thiagofm: I'm pretty sure you're confusing a lot of concepts there
Mutations are inherently not side-effect free, in the sense that you want your counter to keep increasing, i.e. different calls to the same mutation don't return the same result
what needs to be side-effect free is the return value of the parser. This is why we return an :action
thunk instead of the side-effects
in sum, there's absolutely no problem of having side-effects in your mutation, as long as they're encapsulated in the :action
thunk (function with no arguments, e.g. (fn [] (fire-missiles!))
)
Okay then, so it's right. It's just that let's say I didn't have the problem with the "componentWillMount", it would be possible to have a function that with the same arguments always return the same result(or to the :action thunk)
I still don't get much concepts, but the more I wrestle with setTimeout/setInterval, it looks like if om provided some sort of abstraction for those things it would make this sort of stuff possible
@thiagofm: well, Om does render on requestAnimationFrame
, so it's perfectly possible to create an animation loop without setTimeout and setInterval
providing abstractions like those are completely out of scope of what Om intends to be
those fall completely on the user side
That is fine, I mean, I could write my own thing that would abstract that for me. I'm pretty sure as om next evolves people will come up with solutions for problems you could have when trying to write something that does X or Y in om next.
I'm sure that'll be the case
Hi, wonder if anyone can help: I have an om next send fn that triggers an SSE event stream connection. The callback fn is called every time an event is made.
(go-loop [callback-fn (<! ch)]
(-> (new js/EventSource "/stream")
(.addEventListener "message"
(fn [event]
(let [entry (read-string (.-data event))]
(callback-fn {:entries [entry]})))))
(recur (<! ch)))
I want entry
to be appended to entries
in the local app state, but at the moment it is just replacing
it each time with the latest entry.
the reconciler doc string for the send function seems to suggest that it supports this behaviour
@lsnape: seems like you can supply custom merge functions to reconciler
https://github.com/omcljs/om/blob/master/src/main/om/next.cljs#L1767
Try this one: (defn custom-merge-tree [a b] (if (map? a) (merge-with into a b) b))
plug that function as :merge-tree
on your reconciler
@iwankaramazow: ok will do. Was having trouble finding the option to pass in!
@iwankaramazow: @sander great that seems to be having the desired effect. Thanks
:hugging_face: 😄
Can't seem to be able to make the example in https://github.com/omcljs/om/wiki/Remote-Synchronization-Tutorial run 😞
@iwankaramazow: are you going to update https://awkay.github.io/om-tutorial/#!/om_tutorial.G_Remote_Fetch anytime soon?
@thiagofm: you should ask @tony.kay, didn't write it need some extra help?
@thiagofm: A lot going on for me right now. I’ll try to make time to work on some of the unfinished bits of the tutorial in the next month or so.
So another question for the channel
(def init-data
{
:list/users {:selected-user [{:ID 0}]
:users [{:ID 0 :userName "Jimmy"}
{:ID 1 :userName "Robby"}
{:ID 2 :userName "Bobby"}]}})
Again - with this data set - selected-user is local only and users is remote data. The data I want to retrieve/update is the list of users.
What is the best approach to achieve this?@jamesmintram: That is a really broad question in the context of Om, so you should first try to understand all the core building blocks via the tutorials. Om does not have a model where you “retrieve/update” things like you might when using AJAX and jQuery. It is a wholesale different approach. The basic answer (if you know the blocks) will involve: run a transact! that mutates something and specifies “remote true” as part of the mutation (that would get you update behavior, and even lazy loading). Initial reads could look to see if the state is missing, and indicate “remote true” for processing the UI query.
In our applications, we’ve dropped doing remote queries at all, and do all loads via “lazy loading” that is done via transact!…a generalization we’re quite happy with.
@tony.kay: Yeah, the question was a little broad. I currently having it working with :list/users. The thing I am trying to work out, is triggering a read for a nested item (in this case :list/users)
“triggering” almost always means: transact! a marker into the state, then during the next call of the parser (which is triggered by that), use that marker to decide what needs to remote
OK - but I have seen Om next apps laid out, leads to reads at the “root” of the data -
OK thanks. Part of me was thinking, that in the :list/users read function, I would call another function which would deal with :users (Making this stuff composable)
you can work with the parser recursively, if that's what you're after
The problem we ran into was more interesting: our UI queries are frequently different from what we actually want to ask the server. So, we hacked into the send and just ignore the “remote read” support of Om altogether. Our transacts specify what we want to read, and then we post-process that into the app state on response.
@iwankaramazow: That sounds like what I am after - any examples of this?
The UI queries are still invaluable for the UI, we just didn’t find them as useful for remote interactions.
@jamesmintram: @tony.kay 's tutorial has all you're looking for 😄
@tony.kay: if I understand correct, you don't use process-roots
any more?
Haha, I have been reading those - but haven’t come across that yet
@iwankaramazow: correct
@jamesmintram: The tutorial has notes on a lot of this stuff, but it isn’t fleshed out well…there is also a demo app buried in there you can run that simulates a remote. worth playing with. I think it still works
Thanks! I am checking it out now
Also worth reading the tests in the Om source code: https://github.com/omcljs/om/blob/master/src/test/om/next/tests.cljs#L1206
Oh yeah.. I somehow always forget to checkout tests. They will probably answer all of my questions.
@tony.kay: where does the post-processing of your "transact reads" happen?
So, here’s the trick: Our load “mutation” adds markers to the top level app state that record (via :action
) what to load (e.g. the query to run, post processing lambda), they also return :remote true
. This causes send to run. Send, in turn, sees that it was a load mutation, and instead of sending that remotely, it looks in the app state and gathers up the markers.
When the response comes back, we just merge it in, and then run any post-processing lambda specified.
There’s a bit more plumbing, but that is it in a nutshell…some really cool stuff fell out, too, like load-field
as a mutation that can lazy-load some nested bit of UI (like comments on an entry).
(load-field this :comments)
helper function that runs transact…also leads to being able to auto-render a spinner on that element in the UI
Ah that sounds really awesome
@iwankaramazow sorry, thought it was you! Thanks for offering help, but I'll give it a try to learn further, so perhaps I can ask you harder questions later 😉 -- @tony.kay great! The other parts of the tutorial are looking good, certainly of big help for newcomers
@tony.kay: I wonder if @anmonteiro's latest work about making dynamic queries a lot more feasible helps with that problem. process-roots
works pretty well for me (and supplying a list of keys that are remote, my remote read just looks for those), but I can see how changing queries over time is awkward right now.
theoretically your transacts just turns into set-query!
calls, and I think my system would be similar then, but my app isn't extremely complex yet so maybe it won't scale like I'm thinking
Honestly this reminds me a lot of what I was doing with Netflix's Falcor
For example you can't fetch a 'whole' list, it was either a range where you paginated or 'overfetched' with 'nil atoms'.
Basically the only thing you could resort to in that scenario was a 'call', i.e. Om's transact equivalent.
@iwankaramazow: unsurprising since a lot of om next was inspired by falcor
How are people handling Om tempids when transacting against datomic?
converting om tempids to datomic tempids?
@jlongster: my ideas have little to do with changing queries over time. It has more to do with having multiple views of the data, none of which represent what you actually want to ask the server for (e.g. you might want a combination of things that exists in no specific place in the UI). @anmonteiro: Yep, that’s what we do
map from omid -> datomic tempid -> transaction -> dtempid -> realid -> map from omid -> realid
@jlongster: In the best case, you can load using a query that is on the UI (easy enough to get). In the worst case, you define your query via defui that have no UI (for normalization only) and use that instead.
@tony.kay: that's what I'm doing too
was wondering if there were options that didn't involve so many steps
I'll write my own helper then