This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2015-10-19
Channels
- # admin-announcements (2)
- # beginners (24)
- # boot (36)
- # business (1)
- # cbus (3)
- # cider (22)
- # cljs-dev (91)
- # clojure (101)
- # clojure-canada (9)
- # clojure-china (3)
- # clojure-czech (21)
- # clojure-nl (3)
- # clojure-russia (131)
- # clojure-sg (5)
- # clojure-uk (9)
- # clojure-ukraine (4)
- # clojure-za (2)
- # clojurebridge (18)
- # clojurescript (333)
- # clojurex (6)
- # devcards (1)
- # events (37)
- # hoplon (15)
- # ldnclj (23)
- # luminus (3)
- # off-topic (41)
- # om (258)
- # onyx (20)
- # re-frame (46)
- # reagent (7)
- # spacemacs (2)
Hi, is this the right place for me to come for help using om next, or is this channel meant for project contributors?
I'm new to om (and pretty new to clojure) and I'm trying to use devcards, but I can't figure out how to put an om component that has queries and mutators into a card
yes, you can ask for help, I can't really give much help though, and dnolen is busy today
great, thanks. I'll probably just pop back in tomorrow then, it looks like sunday night may not be the best time to find assistance.
Short question, I have a mutator like this:
(defmethod mutate 'window
[{:keys [state]} _ {:keys [w h]}]
{:action #(swap! state assoc :window {:w w :h h})})
and transacting like this:
(om/transact! this
`[(window {:w ~w :h ~h})])
why do I get No method in multimethod 'dmedit-om.parser/mutate' for dispatch value: dmedit-om.core/window
?I got that part, but why is it not able to resolve it correctly to my parser namespace? I checked the om-next-demo to see if I did something wrong but he's doing it the same way:
(defmethod mutate 'todos/clear
:onClick (fn [_] (om/transact! todos `[(todos/clear)]))}
https://github.com/omcljs/om/wiki/Quick-Start-%28om.next%29#parameterizing-your-components
dvcrn: so the parser only has 'dmedit-om.parser/window defined, not 'dmedit-om.core/window. things are easier when you make the ns explicit like in om-next-demo
but that brings me to the same question again. I am working with codemirror and when the codemirror component is mounted, it will push the actual codemirror instance into the state. Plus every time when you type something, that is being reflected in the state as well. With the example we had yesterday, I would build my state similar to my component tree, so these things would go inside :codemirror, probably. If I have a component X now and that component wants to use the text that the user wrote, it will have to access either the text state or the codemirror instance state which is inside :codemirror, but my rootcomponent would only pass :codemirror to the codemirror component and not to my other one. (ping @sander)
I think the result we came up was that the mutator will have to add this key inside both state keys, right?
soo I went back to the very first tutorial and I think the difficult thing (for me) is to understand how I can request state through the subcomponent.
The first ever example is using a counter that is requesting '[:count]
and mutates on that.
Now imagine I have a second component called Counter2. Everything same but it writes and mutates '[:count2]
and is being mounted as a sub-component of Counter.
(defn read [{:keys [state] :as env} key params]
(let [st @state]
(if-let [[_ value] (find st key)]
{:value value}
{:value :not-found})))
(defmulti mutate om/dispatch)
(defmethod mutate 'increment
[{:keys [state] :as env} key params]
{:value [:count]
:action #(swap! state update-in [:count] inc)})
(defmethod mutate 'increment2
[{:keys [state] :as env} key params]
{:value [:count2]
:action #(swap! state update-in [:count2] inc)})
(defui Counter2
static om/IQuery
(query [this]
[:count2])
Object
(render [this]
(let [{:keys [count]} (om/props this)]
(dom/div nil
(dom/span nil (str "Count: " count))
(dom/button
#js {:onClick
(fn [e] (om/transact! this '[(increment2)]))}
"Click me!")))))
(def counter2 (om/factory Counter2))
(defui Counter
static om/IQuery
(query [this]
'[:count {:count2 ~(om/get-query Counter2)}])
Object
(render [this]
(let [{:keys [count]} (om/props this)]
(dom/div nil
(dom/span nil (str "Count: " count))
(dom/button
#js {:onClick
(fn [e] (om/transact! this '[(increment)]))}
"Click me!")
(counter2)))))
The important bit is
'[:count {:count2 ~(om/get-query Counter2)}])
I want to tell om to filter on the subquery, but with this syntax it would use the reader for :count2
and completely ignores the subquery. I could even write
'[:count {:count2 "om is weird"}])
and it would still have the same effect... anyone know what I mean?the Counter2 query only specifies attributes, so its only function is to help normalize the data
but how would I collect that inside Counter, query for the data and pass it to Counter2 by actually using the query Counter2 provides
that would pass the entire props and not just :count2
. plus that only works because I use {:count2 ~(om/get-query Counter2)}
inside my query which right now would be the same as just writing [:count :count2]
. It is not using the query that counter2 provides
@dvcrn: sorry, I have actually no idea how to do that. thought I understood it but apparently I don't
and indeed, seems you don't need the subquery for Counter2 if you don't use normalization and you don't use remote fetching
I also tried the components / identity / normalization tutorial but it seems normalization is broken in master.
Really? I get normalized data that looks like this: {:list/one [nil nil nil], :list/two [nil nil nil], nil {nil {:name Jeff, :points 0}}}
.
@gabe: not recommended, but also I don’t see why you would need to do that given the you can get them via the api
@sander: @dvcrn: Right, it works fine when I follow the tutorial instructions. My project is using boot and perhaps something in that setup breaks things.
@dnolen: after finishing the quick start I added a slider to dynamically change the end
query parameter. but i need to know the value of start
so I can pass both to set-params!
(defn get-params
"Get the query parameters of the component."
[component]
{:pre [(component? component)]}
(-> component get-reconciler :config :state :params))
Oh dear. I've had so many problems with om recently, which was quite frustrating, and now it turns out the cljs version in my project was simply too old (1.7.48).
@dnolen: I gotta say though. I’m really digging the level of indirection w/ the notion of updating query params instead of direct access of the global state
yes also I’ll probably start encouraging people use them to creates views over large amounts of data
@gabe: yeah will need to hammock that one for a bit this week, that’s really less about “Dynamic Queries” then it is about Unions in queries
If I have something like {:items [{:id 1 :properties {:name "Foo" :count 2}} {:id 2 :properties {:name "Bar" :count 5}}]}
in my app state, how would I query e.g. for :id and :name if I'm not interested in :count? Is there a way to do that?
(I can make the query [:id :properties]
which will give me everything and seems to ensure that :properties
is included in the normalized data inside the reconciler.)
if you think of queries & parsing as really fancy client side routing this all becomes a lot clearer how it’s supposed to work
@dnolen: You mean "routing" not like URL routing but routing as in how data is routed through the application into components / the DOM and back?
you could even say Om Next is somewhat inspired by Ember.js by putting routing at the center of the design
@gabe: there you go https://github.com/omcljs/om/commit/31f0f125834ae3f4f633b7c55b8fe09fd43c5b46
The identity tutorial doesn't use an atom as init-data
and as the :state
it passes into the reconciler. If I use an atom instead, the reconciler doesn't normalize its data. Is this expected?
Ah, the tutorial states that if it's an atom the reconciler assumes it's already normalized. Ok
@jannis: or you could pass a denormalized atom and set :normalize to true
the reconciler accepts that option
@anmonteiro: Ah, cool, that works. Thanks
@dnolen: (get-params this)
is returning [:app/title (:animals/list {:start ?start, :end ?end})]
on first render and {:start 0, :end 10}
the rest of the time.
for anyone wanting to use sablono w/ om.next @r0man update react on his react-0.14 branch a couple of days ago
> looking fwd to https://github.com/omcljs/om/wiki/Dynamic-Queries
The one I'm looking forward to most is the remote synchronization tutorial, I have to say I still don't have a good intuitive grasp for how queries are meant to be split between client & server state and how the are synced
@dnolen: Curious about intended design in the large for om-next. Is there a composition story so that there can be "sub roots"? When I start to think about laying out a single-page app, I'm seeing top-level queries like: [{:menu-bar (om/get-query Menu) } {:toolbar (om/get-query Toolbar)} ...] which then means you write a pretty large recursive parser (not necessarily bad). I know you're working on the Union syntax for queries to deal with the fact that sub-components may want to swap in/out, but I am interested in knowing if that is the intended application structure. I guess there could be multiple top-level DOM elements with separate Root/reconciler/indexer, but then there would probably need to be a way for them to communicate with each other. No need to explain the whole plan, but we're trying to plan apps in the large and I want to make sure I've got the entire picture.
@tony.kay: I've just been playing a bit with om next, but I've found you can write a read method for some 'virtual key' like :app/root
and have that parse the selector as if it was at the root. I have no idea what implication that has on mutations though. This implementation has been working for me so far: https://www.refheap.com/110805
The indexer does a static walk of the data structure from root (via meta data on the query fragments)
I guess as long as the Root still nests in :app/root in the primary query it would work
I'm also trying to get my head around the caching and remote stuff, as one of our desires is to get a shared web-socket and server-push story going. So if anyone has comments on that I'd love to hear them.
I've got a solution working nicely with sente and multimethods to multiplex the comms on websocket..but I have not even started working on how to make them work with om-next
@tony.kay: union is not about swapping in/out it’s just that something may show different types of things
absolutely nothing prevents a component from getting it’s own stuff unrelated to anything above it
I've been reading the source pretty heavily for the past few days. I don't see how a new root can get into the structure. I'd be more than happy to write a full tutorial on it if you can give me some pointers
I see the parser getting called from add-root!, and I see how to follow the trail down...but I am stumped on getting a new subroot plugged in at an arbitrary point
@noonian: What do you mean by “virtual key”?
@gardnervickers: a key that doesn't actually exist in your state atom, but acts like it does exist and the value is the entire state atom. Like a symlink on a file system so if you query for [{:app/root [:app/title]}]
and your state looks like (atom {:app/title "Some Cool App"})
your component will get passed the props {:app/root {:app/title "Some Cool App"}}
I'm trying to use om next with devcards, and I can make a basic component that doesn't have a query appear in a card, but I can't figure out how to make a component that uses a query to work. I'm trying to embed the result of this tutorial into a card https://github.com/omcljs/om/wiki/Components%2C-Identity-%26-Normalization and I can get the RootView to render, but its subcomponents won't show up
this is the code I'm putting in the devcard:
(defcard run-om-tutorial
"Run the om next tutorial as a devcard"
(let [tut-state (atom tut2/init-data)
query (om/normalize tut2/RootView tut2/init-data)
parser (om/parser {:read tut2/read})
view (om/factory tut2/RootView)
view-data (parser {:state tut-state} query)]
(print view-data)
(view nil view-data)))
I see "List A" and "List B" headers, but nothing renders for the listviews inside of them
@josephjnk: does devcards work with React 0.14?
@josephjnk: it should work from devcards 0.2.0-5
If it helps, this is what that print line outputs:
{[:list/one [[:person/by-name John] [:person/by-name Mary] [:person/by-name Bob]]] [nil nil nil], [:list/two [[:person/by-name Mary] [:person/by-name Gwen] [:person/by-name Jeff]]] [nil nil nil]}
I think that I'm not correctly getting the subqueries? Since there's no points in there
At least i’ve been using it fine but there may be unknown issues. Are you excluding react from devcards in project.clj?
for devcards I've had success using the devcards dom-node helper and calling om/add-root! on the node that is passed to the function passed to dom-node
https://github.com/minimal/om-next-tut/blob/master/src/om_next_tut/datascript.cljs#L72-L75
@josephjnk: https://github.com/minimal/om-next-tut/blob/master/src/om_next_tut/identity.cljs#L155
@minimal: I'm still doing something wrong, but I'll just start from your tutorial and work from there. Thanks for your help.
@a.espolov: just direct questions to the channel please
@a.espolov: Use a map as the arguments to a call '[(do-thing { :a 1 })]
only if your mutate function is already namespace qualified otherwise syntax quote with qualify it with the namespace its defined in
So, I'm still struggling with David's comment "the design already supports subroots. Absolutely nothing prevents a component from getting it’s own stuff unrelated to anything above it". Does anyone see how to do that? As far as I can tell, all subcomponent queries have to be joined to the Root component through recursive use, or they are disconnected from the whole thing. The parser only runs on the root query. Recursion in the parser is an implementation detail of parsing. The reconciler has an add-root!, which is for the top level, and I see no way of telling the reconciler/indexer "I'm here" from some arbitrary subcomponent besides declaring a query that my parent pulls into its query.
So if I want, say, some report that needs to pull a large clump of data over when it appears on the screen, I don't want it running as part of the top level parse. I guess I could patch it in via query parameters on demand...is that the technique?
also I would keep the query bits separate from the components bits to keep the question clear
that sounds like a dynamic query in the component that is conditionally showing the report component
@dnolen: I see no way to trigger a parse/query on a component other than hooking it up to the reconciler
@tony.kay: I'm still waiting on the dynamic queries tutorial 😛. I have a dynamic query working but it feels a little dirty because I am accessing the app state directly and not through props or queries or anything to get the data I need in the query/query-params functions.
@dnolen: Are all query fragments required to join all the way to the Root at the reconciler?
you prove this to yourself just by using a parser on some data where subquery key reads some arbitrary thing from the state
ooof. Sorry for being dense: 1. I understand that the parser is a standalone thing that is passed env -> key-> params and returns map (with :value key). 2. I understand that components can declare a query hooked statically to the component class...such that I can arbitrarily ask a component for the query fragment So, are you saying that in a render method I could do something like this: (render [this] (subcomponent (my-parser some-state (om/query Subcomponent)))
Does the reconciler re-normalize the state data after mutations if :normalize true
is passed to it?
@tony.kay: If you get the subcomponent's query in query-params or query you can compose it there instead of calling the parser yourself in render. By dynamically deciding which components to both render and compose their subqueries, and then by using the "virtual key to the root state" trick you can have arbitrary queries against the top-level from sub-components. I am doing this now but I believe it will become clearer once there is more documentation around dynamic queries.
@noonian: Right. That was what I am assuming is the approach, however, the comment "Absolutely nothing prevents a component from getting it’s own stuff unrelated to anything above it" along with David's response to my direct question about things needing to reach all the way to the root confuses me....because the approach you are using always causes the top-level to be involved...the virtual root key just allows you to "loop back" to the top of the real state.
@tony.kay: that pseudo-code doesn’t make sense to me. I don’t see how this is related to your question
@tony.kay: thats true but that was my desired behavior hehe. I think that its a very case by case basis and that approach makes sense for a page-like component where you have no idea what each page wants so give them the top-level. Alternatively you could let the sub-components not access the top-level by default but if they needed to they could just query for the root themselves.
@dnolen: I have some code that polls a backend for data and then tries to insert it into the app state via (om/transact! reconciler ...)
. However, the data it receives from the backend is not normalized. Do I have to e.g. dedup/normalize this data myself before I can insert it into the state in the mutation?
yeah it doesn't change the state but the props you receive present the illusion that the state is a self recursive structure
@jannis: you will have to normalize yourself if you’re going to use transact, there’s also merge!
which probably needs some tire-kicking
but this "tree for the UI" is peppered with persistent state...perhaps I am misunderstanding the picture. I assumed that any data you want in the UI is coming through these queries.
@dnolen: Ok. What does the delta to be passed to merge!
look like? Is it just {:objects [...]}
if I want to update :objects
in the app state?
I think of the parser as taking the app state and a query and building the tree of props that will get passed to the tree of rendered components. So thats two trees that are distinct from the app-state tree
so only the state atom is "persistent" as you say, and the tree of data from the parser is just built up in order to render the components
Yep, that is my understanding as well. I'm going to get some food and consider how I might do a better job of asking my question...or perhaps the light will come on. I've got the core functionality. I'm more on to what you were saying about dynamic queries and choosing to render things.
a ref is a single item update, a mergeable result generally needs to have the same shape as the total query if not all the same keys
I'm not sure how either would have to look like when being passed to merge!
. Would a single item update be something like {[:person/by-name "John"] {:name "John" :points 5}}
? Or completely different?
Before we dive into this: the single item update would only work if "John" already exists in the state, I assume?
Hm, no, it doesn't matter. If I do (om/merge! reconciler {[:person/by-name "John"] {:name "John" :points 0}})
I get an error: Invalid key [:person/by-name "John"], key must be ref or keyword
@dnolen: could you explain the difference between transacting on a (possiby nested) component or transacting on the reconciler? It seems like the result value of a mutation triggers rendering with different props depending on where in the UI tree the transact! is called.
and is it idiomatic to transact on the reconciler from within a component using om/get-reconciler?
@noonian: OK...lunch helped. I see where I was confused. I talked through it with coworkers and I see why my questions were confusing...your comment on the UI tree helped it fall into place. The app state is just some bag of data that changes over time. The UI tree is how I want to view that app state at any given time. Dynamic queries give us the flexibility to morph the UI in arbitrary ways, which may involve arbitrary queries to the server at any time (which if they update app state will trigger re-renders).
I was trying to tie the component queries to server queries (directly instead of indirectly).
@noonian: transacting on the reconciler is just a convenience for “remote control” - i.e. mutations from processes that are not a triggered from Om Next components
@dnolen: I'll play with this more tomorrow and try to come up with some minimalistic demo code.
I'm finding that when I transact on my component instance it causes the component to re-render once with nil props and then stop rendering changes. It doesn't happen when transacting on the reconciler. Its probably a bug on my end but I
I'm also not sure how merge!
could be used to remove things from the state. If merge!
is used to update the state based on data retrieved from a backend (e.g. a REST service), things might disappear.
@jannis leaning towards just using nil
for property deletions, seems fine to me far as UI problems
When your REST service simply serves a list of objects, how do you know which old ones in your app state to set to nil
via merge!
? Query the current state, check which ones are no longer present in the list served by the backend, assemble a merge!
delta accordingly?