This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2015-10-27
Channels
- # beginners (22)
- # boot (652)
- # boulder-clojurians (1)
- # cider (19)
- # cljs-dev (3)
- # clojure (158)
- # clojure-dev (8)
- # clojure-nl (1)
- # clojure-poland (5)
- # clojure-russia (27)
- # clojure-sg (3)
- # clojure-za (4)
- # clojurescript (44)
- # community-development (2)
- # core-async (17)
- # core-logic (10)
- # css (1)
- # cursive (35)
- # data-science (5)
- # datascript (1)
- # datomic (90)
- # editors-rus (3)
- # events (3)
- # hoplon (90)
- # ldnclj (19)
- # lein-figwheel (2)
- # leiningen (1)
- # om (225)
- # reagent (1)
- # uncomplicate (27)
Ha, in my kanban demo app (I'll share it tomorrow), adding an card editor dialog, with all the reads and mutations involved, requires less om.next code than it requires CSS to look nice. Pretty awesome.
I may not have understand queries in every detail yet but I'm mostly adding components, mutations without looking at the result and when I'm done saving the last change, I look at the app and it usually just works. Development is becoming really intuitive and predictable.
@jannis to me one the biggest value propositions of this work is that Om apps should have a very regular, predictable structure
you should be able to jump into an Om application and have a really good idea what’s going on - yet still provide enough flexibility for people to solve their own problems however they see fit.
I'm curious to see what structures other people come up and how easy or difficult it will be to understand those.
but still parser + queries + stateless components give you a lot of guideposts when reading some UI code
Ha. The total app is ~ 377 lines of code, the CSS is 236. And that's with a menu to choose kanban boards, a view of the active board with lanes and cards in each lane, with card DND between lanes, and with a basic dialog for editing cards. And initial data to have something to look at.
Just realized the middleware pattern around the parser read and mutate functions is very powerful. With all the context info you have access to (ref, query, params, etc.) you can do some really interesting things: like decoupling your validations from your parsing logic by passing model definitions into middleware.
though I had to patch https://github.com/omcljs/om/blob/master/src/main/om/next.cljs#L626 - (gdom/isElement)
won't work on native
@blissdev: it's a emacs configuration kit called spacemacs - https://github.com/syl20bnr/spacemacs/ basically emacs pre-configured with vim keybindings and a ton of support like clojure(script) out of the box
ah ok, very cool, i actually use spacemacs, was curious if you had possibly made intellij look the same
haha oh. I have a intellij license but once you get used to the superb keybindings of spacemacs, it's hard to switch to something else
it has a ton of clojure support and integrates very well with cider https://github.com/syl20bnr/spacemacs/tree/develop/layers/+lang/clojure
that too, yes. Figwheel can embed nrepl. That makes it easy to connect to it with cider (or anything else)
question again about normalisation... I have a component that looks like this:
(defui RootComponent
static om/IQuery
(query [this]
(let [subquery (om/get-query Person)]
`[:app/people ~subquery])))
Person looks like this:
(defui Person
static om/Ident
(ident [this {:keys [name]}]
[:person/by-name name])
static om/IQuery
(query [this]
'[:name]))
my reader is very generic (for now) and simply checks if the key exists in state. If it doesn't, return :remote true
. So by default it will automatically fetch remotely because my app-state is empty.
The reconciler is then calling my function I specified for :send
and fetches some data from my server, looking like this: '{:app/people [{:age 22, :name David} {:age 29, :name Fred} {:age 20, :name Paul} {:age 22, :name Jiyoon} {:age 23, :name Bob} {:age 28, :name Mark} {:age 24, :name Jiwon} {:age 28, :name Dude}]}'
If I don't tell it to normalize, this data is being reflected in the app-state correctly, however when I add :normalize true
to the reconciler to tell it to normalize, the resulting app-state is always empty.
Where is my mistake?@dnolen @mhuebert Hi ! I am very interested in your conversation about recursion, could you keep me in the feedback loop if you progress on that matter ? It would be awesome
@hmadelaine: will do, will need to think about it some more.
for a blog article i am collecting some patterns that i keep repeating in my om.next app.
(render [this]
(let [{:keys [app/item-a app/item-b]} (om/props this)]
(html
[:div
(some-> item-a
component-a)
(some-> item-b
component-b)])))
i am unsure about this one... is there a better version for this?
I don’t use those things, and since I don’t I would probably make a simple function helper
@dnolen: not sure if you're familiar with the CircleCI Om (Legacy) architecture
where they basically have a "common" component that wraps all the pages; it interprets a :navigation-point
to decide which sub-component to render
was wondering how to use this notion with Om Next (haven't tried it yet)
basically my hypothetical problem is that this wrapper would have no queries (?) but would be the root component (I think this is some kind of contradiction)
or maybe it would have to dynamically get the queries of the components it would host
looking for some pointers
@anmonteiro: the root component does have a query, whatever the navigation is and whatever the subcomponents need.
Navigation-point is a keyword that's resolved to a component statically
@dnolen: so the root would have the navigation-point and get whichever component's query dynamically?
@anmonteiro: so put that in the query and use om.next/subquery
Alright I'll try and put together some code before I ask any questions that might not even arise
Here's an initial version of my Kanban demo app for Om Next: http://jannis.github.io/om-next-kanban-demo/ (DND may not work on Firefox and IE, hopefully on other browsers though). I'll see about a write-up next.
Source code is here: https://github.com/jannis/om-next-kanban-demo
How should subquery be called if we dont set react refs? om/subquery this nil MyComponent
?
Or is the ref explicitly needed?
Looking through the source it seems pre conditions won't pass if I pass null
(No access to computer atm)
@dnolen: Sweet It seems Firefox has a problem with how React translates
:onDrag*
or something but I haven't investigated yet. Glad it works with Safari.
@anmonteiro: think for a moment
Looking at the parser code, an error thrown by the :action
thunk gets overwritten when :value
exists. Presumably the error should be retained so we can tell that the mutation failed?
https://github.com/omcljs/om/blob/master/src/main/om/next/impl/parser.cljc#L109-L111
@dnolen: I'm sorry to waste your time, will look into it when I have computer access; just didn't want to lose my thoughts
The company I work for made an app called http://www.sweepboard.com as a side project a while back. It's pretty dated and alternative services like http://waffle.io are probably better but we still have people signing up naturally and some even asking for enterprise versions so we were thinking about doing a rewrite.
You mentioned "almost more lines of css than cljs" and that might be a good argument for me to bring forth when we discuss tech
Maybe someone should make a round version of the official om logo, so it looks more like the clojure one. https://avatars0.githubusercontent.com/u/10822115?v=3&s=200 Like that but round.
Hi everyone... I know a parent's iQuery function should not "muck with" the result of a child's iQuery function... but in the remote sync tutorial there is the following code:
(query [this]
(zipmap
[:dashboard/post :dashboard/photo :dashboard/graphic]
(map #(conj % :favorites)
[(om/get-query Post)
(om/get-query Photo)
(om/get-query Graphic)])))
Is it OK in this case because "conj" preserves metadata put onto the get-query calls of the children?
Or am I misunderstanding the point of saying that "a parent needs to return its own iQuery" and "a parent should not muck with the query of its children?"
@drcode: union queries are special case, that component isn’t a real standalone thing, just a union component
and it’s not breaking the more important rule of trying to use a child query as it’s own
there’s really no better explanation that queries are like a typing assignment to a tree
when you lie about the types, the things that Om Next can assume about what’s happening breaks down
Don't know why i'm having difficulty with this- But that explanation helps, thanks again
@drcode, in the example from the tutorial it still creates a new (union) query where it uses the childs query as an (extended) subquery
@dot_treo: Yes, I do see that, essentially every component needs it's own "node" (or "nodes") in the query tree or bad things happen. And, since the "Query selector" model gives an app complete freedom to define the tree structure to fit its needs, this rule is easy to maintian
@dnolen: thanks
@bbss: The "almost more CSS than code" argument doesn't hold very long. I mean, this is just a really simple app with no remote syncing, multi-client conflict handling etc. etc. Nevertheless, I think apps can be written in a concise way with Om Next - and "less code ≈ fewer bugs" is a factor in management decisions. ;) The predictability (in development and issue analysis) is probably the stronger argument though.
I have a subcomponent that I am using to iterate over a sequence. Once I reach the end, I have my mutate function change a top level value that is used for navigation. I include the key in the “value:” pair returned from the mutate function. The root component has a query for that value, but the UI is not updating. I am guessing that this is because the transact! was on the child component, which does not have that query. Any thoughts?
@monjohn Try passing the query key to om/transact!
, e.g. by passing in [(some/action ...) :app/active-object]
.
I've made a habit out of only transacting in the component that has the corresponding query though, and pass a callback down to subcomponents. Here is an example: https://github.com/Jannis/om-next-kanban-demo/blob/master/src/kanban/app.cljs#L62
There :boards/active
represents the current object being rendered and menu items use a callback to trigger a transaction in App, which is also where :boards/active
is queried.
@jannis: So you are saying to pass the query key to the transact! function in the vector after the mutation functions?
I'm not sure why it's needed (or why returning the key via :value
doesn't have that effect) but I had the same problem and this worked.
@jannis: https://gist.github.com/monjohn/b90a18d551e7ed7403f0 I put comment on top to explain where transact! is being called
@jannis: I've seen the same problem on mutate :value seeming to be ignored...had not explored it yet, but it surprised me too...possibly a bug
@jannis if you have a second, I’m curious what the purpose is of this query definition https://github.com/Jannis/om-next-kanban-demo/blob/b1b1612cff58931925a96ab7c30098030a9c8cec/src/kanban/components/boards_menu.cljs#L10-L12 it doesn’t look like it gets used anywhere with get-query. Does om.next use it under the hood somehow?
@tyler: Good point. It's not used anywhere. {:boards (om/get-query Board)}
in App somehow ensures that boards are normalized properly and since the query fields of Board
are a superset of BoardMenuItem
it works. It doesn't feel right though. I wonder if adding {:boards (om/get-query BoardMenuItem)}
to App would be valid or whether it would conflict with {:boards (om/get-query Board)}
...
@monjohn: I've noticed you return values like :value [:word-list :correct]
even though there is no :word-list
at the top level of the app state. I'm not sure but I don't think :value
is interpreted relative to the component's query. You might reuse the same mutation in another component with a different query, where :word-list
does not exist, for instance.
Anyway, adding :current-list
to the mutation updates the UI (because that's what changes when you increment). Might also want to pass :correct
to the transaction.
If you look at the indexer, it indexes all components that rely on a particular key...does not matter the query depth. The components that ask for a "key" will end up in the index
which is why you should probably namespace the keywords...otherwise you end up associating a query keyword with components that ask for different things
@jannis That works! Thanks. Yeah, I can’t get my head around :value and how it gets match up with queries.
@jannis: so, when you returns keys in the :value, it just looks up the components that are in that index at that key. See my prior comment lead-in
@tony.kay: Ok. But if read
returns nothing for :word-list
, returning {:value [:word-list]}
will not find any corresponding components, I assume?
Om doesn't look at your read result...you're responsible for mucking with the state. The list of keywords goes into a list of "things that changed". The indexer is then used to look up those components. I don't think read has anything at all to do with it (how could it?). The indexer indexes components as they are mounted...this includes the keywords that are listed in their query. I have a section called "The Indexer" in https://github.com/awkay/om/wiki/Om-Next-Overview that might help.
Right, I don't understand the indexer well enough yet to talk about this, so I'll try to refrain from adding extra confusion 😉
Yeah, it is really helpful to just pprint the state of the index...that cleared a bunch of questions up for me.
cause then you see how it indexes those keywords that mutate is supposed to be returning
it's just maps...so trivial to read (except for the query index...which is a zipper)
oh, thanks. While we're exchanging that kind of thing: I was soooo excited to see @jannis kanban demo...gave me some great ideas
Oh, I just really appreciated seeing how someone else is structuring the app. Nice and clean. I sometimes forget OO layout for components can be nice in CLJS too. E.g. I was tending to put functions in a let (of render) instead of just making them members of the class.
I used to put those extra functions outside components but I like to keep things closer together. Perhaps it's because I'm new to functional programming and am used to the "global functions/variables/etc. are bad" mantra from the OO world. 😉
right, if it isn't reusable, no need to pollute the global space. I think we all agree on that. Thus the tendency to "let" things...but in the case where what you're writing decomposes nicely on OO lines, I see nothing wrong with leveraging techniques that everyone understands clearly...methods on a class. Gives nice localized reusability
Global state is bad. Referentially-transparent global functions are fine.
@tony.kay let would be fine as well, except it would clutter render a bit more... perhaps. Move a few definitions out of the way and it becomes more readable / understandable. I think either way is good. :)
@jannis: That's just it...the let is "fine", but I found the OO technique in the context of a React class just reads better.
that this doesn’t change anything seems non-sensical until you think about actual remotes
@dnolen: I may have to wait for a few more examples or try to understand the code before I understand what exactly happens with :value
after it is returned. When I started working with Om Next, I thought that if a mutation returned {:value [:foo] ...}
, all components that ask for the key :foo
in their queries would be rerendered. After working with it more, it turns :foo
has to be appended to the transaction via (om/transact! this '[(do/something ...) :foo])
for that to happen. That leaves me confused even after your explanation.
as the writer of the client you can decide which of those keys you’ll actually want to read
If :value
is thrown away, how does its presence or content have any effect on what clients download?
so how to force a render for a component that depends on data that changed from a transaction but isn't the component initiating the transaction?
Ok. So if I'm a developer and I want to perform the mutation 'do/something
, I can look at its implementation and :value
in there to see what keys I could pass to om/transact!
because I'm interested in them. {:value [:foo :bar]}
means (om/transact! '[(do/something ...) :baz])
makes no sense.
I'm feeling anxious about code that is ignored...really does lead to misunderstandings. But...opinions are like...
however much time people have spent thinking about this … I guarantee Relay & Falcor have spent 10X longer
this makes a ton of sense. i was trying to figure out why my the keys specified in my server side :value
weren’t getting re-read. client has to specify them in the transact!
call
I think some of the confusion arises in that there aren’t examples in the quickstarts that showed passing the queries to transact! so we assumed that that :value must signal the repaint. Or I should just speak for myself.
no Om Next remote example showing the value of doing it this way (even though this concept is well covered in Relay & Falcor)
Oh right, because by the time the "defui" is in the browser it's now js - all these macros happen at author time (om/clojure newbie if you hadn't guessed)
@bones: also it’s used on the server side to provide a service endpoint for a client parser e.g https://github.com/swannodette/om-next-demo/blob/master/todomvc/src/clj/todomvc/parser.clj