form/edit!
is awesome! That worked — thank you! But is there a way to use RAD forms for just one component (of potentially many) on the screen? Like in this marked up screenshot?
(i.e., I’d love to leverage all the RAD form functionality like Save, Undo…)
now, this is something I'm curious about actually. I've been starting to toy with this exact bit of functionality. I haven't gotten far yet, but I think the potentially big issue is the fact that routers can only have one path target (correct me if I'm wrong please 🙂). To avoid that issue you would need to drop down a level in rad and do all the routing yourself, which may disturb some of the load code.
color me wrong 🙂. https://book.fulcrologic.com#_simultaneous_on_screen_routers
Maybe this just requires a bit of tooling in rad to make it easy?
In your e.g. you may still want manual placement control instead of a router though.
dang. I'll need to read the code to get my head around this fully. I'll let you know my findings if someone doesn't post the solution in the interim
This is so helpful — please keep me posted on your progress… I am currently trying to see if I can render it like a regular “defsc” component…. I’ll let you know how it goes.
(…I realize I may be doing something that exposes my total ignorance on how “defsc-forms” actually work.)
That is perfectly possible. I'm just not up to speed on what is(n't) required to make the life cycle work. AFAIK most of the form lifecycle is triggered by the router.
i agree with your assessment
non-library applications should use :widget/id
, readability is very important
OMG. It totally worked!!! What sort of black magic has @tony.kay created!!! Will post more later!!!
A form is a plain defsc component. It just adds routing integration and a state machine. If you want to use it anywhere, you just have to trigger the stuff that the routing would trigger. Trying my best not to tie you down with anything. Everything (I’ve thought of at least) has an escape.
The routing integration is a convenience, not a requirement.
I concur; with one caveat: If you expect your data store to be published for use in federated data system (i.e. you might pair up with other companies and use pathom to combine data sets in a federated graph) then it is quite useful and important to use the longer names. Most people are just building some in-house thing that doesn’t need to integrate that way, so the shorter name does create less mental overhead/work/aliasing
right, that's uncommon in my corner of the world but i'd expect it to be perfectly normal elsewhere... one way of having your short namespace and eating it too could be to map between short and full namespaces at the system boundaries (`:com.mycompany.widget/...` <--> :widget/...
) but now you've got a mapping to maintain and the responsibility to make sure it's applied just where it's needed
yep…aliasing a lot of stuff creates way more headaches than having to type a little more during dev
Maybe I'm exceptionally slow, but this is more or less the example set that I needed for the first 3 months of intermittent learning with fulcro. If this style is helpful to other people, let me know and I'll flesh it out into a blog post. https://github.com/JJ-Atkinson/FulcroTemplate/blob/core-concepts-fast/src/main/app/ui/exampleset_1.cljs
Your screwups are incredibly useful, Gene! We can learn from them how to provide better documentation / learning materials / error messages so keep a track of those! 🙏
Would it help you folks if we made an example set such as this, perhaps leveraging Workspaces, and comment each example to explain what/why it demonstrates?
I like that idea a lot. I’m off work this afternoon, so I’ll probably get the template up. If there is somewhere it is better placed I can move the repo
We could / should make a fulcro-community org on GH..
@genekim haha! I learned about fulcro back in dec 2018... I printed off the full fulcro 2 book. I didn't start building anything meaningful until may this year, so don't feel bad 🙂
Really it needs a writeup beside it to make full sense, but if you've read the first 3 chapters of the book you should be able to understand everything here. This is just a brain dump of examples I used.
the stuff in here matches some of what I've done for learning, nice!
I'm interested in your writeup! It's taking me ages to get my head around Fulcro and I need all the help I can get.
@jatkin — I found this incredibly helpful, and matches what I've been doing over the last couple of days, trying to replicate the "final-form" examples in the video-series repo. if you're slow, I'm as slow as you are. 😂 After 30+ hours, I've finally gotten my first F3 components running with RAD components, and I think I'm starting to understand idents and queries — or at least enough to get some components doing useful things. But my comprehension of idents and queries is probably still only 30% — I screw them up all the time. I would find a blog post incredibly helpful! Thanks for your help and inspiration getting that RAD form wired up earlier today! 🎉
@tony.kay Where is the source code for the RAD.html book? I wanted to send a PR to fix
> * Containers (not yet designed)
to drop the (not yet designed) since they are designed now.
Say I have a component UserPage, which has properties :user/posts
and :user/username
, which is also the ident, and I want to download posts incrementally: download 10 at the start, and then load and append 10 more on a click of a button. The problem is that on server side, it is actually resolved through two resolvers: :user/username -> :user/id, and then :user/id -> :user/posts. I added handling of two params in the second resolver, and set this fn as a query for UserPage:
(fn [] [:user/username `({:user/posts ~(comp/get-query UserPost)} {:limit 10 :until nil})])
This works for the initial load: I just do
(df/load! app [:user/username username] ui-user-page)
and the posts and user info get displayed. Now I don't know how to request more posts from the server: I could just copy the whole query and set a larger limit, which is dumb, or I need to somehow df/load! with a similar request but with :until set, extract just the :user/posts, and target them at the proper location with targeting/append-to. How do I do that? Plus, in reality I also have a bunch of other props loaded in UserPage's query, which I also would not want to reload, but eql/focus-subquery doesn't seem to work with parametrized queries. And I can't pass the params in the last argument to df/load! because they are the params for the whole query, which only the first resolver :user/username -> :user/id gets, and the :user/id -> :user/posts doesn't see those params.
Maybe I am accessing params in pathom wrong? I am doing (-> env :ast :params).Ok, I have changed the query-params-to-env-plugin from rad to also save the :pathom/context key into the current pathom entity, and it works with the load! with targeting and passing the :pathom/context in the params.
Thank you!
Weird that this is such a hard to use usecase.
well, ultimately it is just a matter of getting the server to respond to a query. Most of the hard work is actually done for you.
but you do have to understand how to leverage the elements of the tools. No getting around that core understanding
https://book.fulcrologic.com/#_paginating_large_lists uses a dedicated *Page
component to wrap the paginated list, displaying a page at a time. It loads more through this load!
(df/load! app :paginate/items ListItem {:params {:start start :end end} :target [:page/by-number page-number :page/items]})
Why is this not applicable in your case? And how does the fact that pathom first needs to go from username to user /id complicate it??It is applicable, but do I have to create a global resolver for every such list then?
This seems weird, plus I now have to be conscious about naming it uniquely since if it appears somewhere else, pathom will resolve it.
I see that there is load-field!, which does almost the thing I need, I just need to figure out a way to get a handle on the UserPage component instance I am rendering (I am doing the load! before the component is rendered).
well Pathom has no built-in support for pagination so yes, you need to make a resolver for each paginated list, or autogenerate those based on attributes or whatever.
But this is also more general than pagination, this is required for any query that is nested and needs params.
Ah, I see what you meant, you don't want a global resolver, you want to load the user's posts in a paginated way. So you still need a resolver, ie.e. my ☝️ applies, but it needs not be global. The load will be different. And the question you are trying to answer is what the load will look like, right?
Yes.
load-field! looks as a good solution for loading more posts (while the initial, pre-render load! of UserPage can load the initial set of posts. . When exactly do you want to load the additional posts? <> ie do you have the component instance there? BTW why doesn't :focus on load! work? You don't need to use the low level eql/focus transform?! (and if you do, apply it one leve down, not to the whole (my-query my-params) but just the my-query
1. Promote top level query parameters out of ast and into pathom env. See rad plugins around query-params. The main deal is that Fulcro always attaches params to the top level of the query, but sometimes you want them used by nested ones. So an env plugin that puts them there is necessary.
2. Use params on load that specify pagination, possibly targeted (so you could specify pagination for more than one child at one…maybe not needed): :params {:pagination {:posts {:start 2}}}
kind of thing…
3. Make resolver pagination aware via query-params
in env
Technically EQL supports putting params anywhere, of course, but the load API specifically avoids the issue of anywhere but the “top” because that works in pretty much all the cases with this scheme and avoids the complexity of putting them into the dynamic query of the components at runtime (doable with set-query!
by the way). Fulcro 1 and 2 had parameter placeholder support in the queries, but after several years no one had ever used them, so when I went to 3 I dropped it. I would consider re-adding it if a good usage pattern emerged, but the fact is you always need to issue a load to get the next page, and some other logic to integrate that new page, etc….so you end up having a UISM involved anyway, and explicit params are just less work from a practical perspective
i.e. you used to be able to put [:user/name ({:user/posts [:post/id …]} {:page ?page})]
in a component query, and then use set-query!
to set the ?page
parameter to, say, 5. The problem was that now you have a problem. Until you’ve set the param this is a somewhat “invalid” query on a UI component (you can default page to nil at best). So, now you have to have query param defaults in component options, etc. In some ways it is a more elegant solution, but in practice it just hasn’t been necessary, so I dropped maintaining the feature.
I don’t have time to revisit it anytime soon, and the way we currently do it works well enough in every circumstance I’ve found. YMMV, of course.
Now that I’m revisiting it in my mind it might make sense to just let you set the query params via :params
option of load!
and ignore it in the UI altogether. That that didn’t occur to me before because this is a vestigial feature from Om Next, which did not have load!
.
Now that I’ve said all that, though, this still would not be ideal for your circumstance, because you want to append pages onto a list, not switch to a new page. In that case the query param really makes no sense at all.
So, I’m back to my current design: parameters are orthogonal data that you interpret via your own logic. For “appending a new page to the end” you just need to use what’s provided, probably ideally with a (reusable) UISM
The UISM would be told who the actors are (the parent, and the line item component), which field of the parent is the join, etc. Then all the control logic can live in there, and can be reused for this pattern any time it comes up.
Thank you for the explanations!
I have added the query-params->env from RAD and using load-field successfully, now I am trying to understand how to merge correctly so that the list from the response is appended to the local list, and does not replace it. The response is of the form {[:user/username "user"] {:user/posts [...]}}, and the local data is of the same form, but I want to append the posts instead of replacing them. I didn't get your part about "In that case the query param really makes no sense at all": why is that? How should I do it instead? I'll have to merge in the new data somehow even without the query params though.
I thought maybe pre-merge would do that, but the posts in current-state are an empty vector for some reason.
you probably don’t want load-field, since it assumes it already knows the target
targeting supports append on a to-many edge of to-many results
But targeting needs the result to be of the form {:user/posts [post1 ... postN]} to be able to insert this somewhere?
I know the target on the additional loads where I am using load-field, just capture this in the onclick handler of a button.
I don't know the target on the first load, so using load! for it.
I’d prefer to use the following load:
(load! this :user/posts PostComponent {:target (t/append-to [:user/table id :user/posts]
:params {:user/username "bob" :page n})
the posts resolver on the back-end has an input…is that :user/username?
No, there are two resolvers: :user/username -> :user/id and :user/id -> :user/posts.
got it, that’s fine. You just need to get a resolver that can work for moving the parameter into the current entity input context for pathom
so, you have some choices. One thing you could do is add a resolver that does that (no input, id output), but I don’t know if that would loop or if pathom gives up on a resolver when it fails to resolve…I think it does, just don’t remember. The other thing you could do is add a pathom plugin that would look for a special param that can be used to set up inputs.
in the latter case you’d just use a ns-qualified parameter for triggering that
:params {:pathom/context {:user/username "bob"}}
something like that
I don’t remember the exact magic incantation for moving a known value into the current resolver context, but I’m sure it’s in the book
Hmm, pathom/context seems to work only for idents.