fulcro

Book: http://book.fulcrologic.com, Community Resources: https://fulcro-community.github.io/, RAD book at http://book.fulcrologic.com/RAD.html
genekim 2020-12-31T00:05:10.450Z

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…)

JAtkins 2020-12-31T00:07:37.450400Z

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.

JAtkins 2020-12-31T00:08:39.450800Z

color me wrong 🙂. https://book.fulcrologic.com#_simultaneous_on_screen_routers

JAtkins 2020-12-31T00:09:14.451Z

Maybe this just requires a bit of tooling in rad to make it easy?

JAtkins 2020-12-31T00:09:32.451200Z

In your e.g. you may still want manual placement control instead of a router though.

JAtkins 2020-12-31T00:12:15.451400Z

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

genekim 2020-12-31T00:12:36.451800Z

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.

genekim 2020-12-31T00:13:17.452200Z

(…I realize I may be doing something that exposes my total ignorance on how “defsc-forms” actually work.)

JAtkins 2020-12-31T00:13:58.452400Z

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.

currentoor 2020-12-31T00:24:25.452700Z

i agree with your assessment

currentoor 2020-12-31T00:24:58.452900Z

non-library applications should use :widget/id, readability is very important

👍 2
genekim 2020-12-31T00:49:28.453100Z

OMG. It totally worked!!! What sort of black magic has @tony.kay created!!! Will post more later!!!

❕ 1
tony.kay 2020-12-31T01:14:51.453600Z

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.

tony.kay 2020-12-31T01:15:05.453800Z

The routing integration is a convenience, not a requirement.

tony.kay 2020-12-31T01:17:18.454Z

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

👍 1
lgessler 2020-12-31T01:20:01.454400Z

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

tony.kay 2020-12-31T01:44:32.454600Z

yep…aliasing a lot of stuff creates way more headaches than having to type a little more during dev

JAtkins 2020-12-31T02:34:07.456200Z

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

👍 3
Jakub Holý 2020-12-31T08:00:21.466800Z

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! 🙏

Jakub Holý 2020-12-31T13:29:13.490800Z

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?

👍 1
JAtkins 2020-12-31T16:59:14.494400Z

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

❤️ 1
Jakub Holý 2020-12-31T17:44:51.494700Z

We could / should make a fulcro-community org on GH..

JAtkins 2020-12-31T23:23:56.498900Z

@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 🙂

JAtkins 2020-12-31T02:35:35.456400Z

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.

Mr. Savy 2020-12-31T03:13:14.456700Z

the stuff in here matches some of what I've done for learning, nice!

tobias 2020-12-31T05:49:30.457100Z

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.

genekim 2020-12-31T07:53:43.464100Z

@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! 🎉

Jakub Holý 2020-12-31T08:01:45.468100Z

@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.

Gleb Posobin 2020-12-31T08:14:34.478100Z

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).

Gleb Posobin 2021-01-02T17:09:18.027200Z

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.

1
Gleb Posobin 2021-01-02T17:09:23.027400Z

Thank you!

Gleb Posobin 2021-01-02T17:11:04.027600Z

Weird that this is such a hard to use usecase.

tony.kay 2021-01-02T17:54:14.030700Z

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.

tony.kay 2021-01-02T17:54:50.030900Z

but you do have to understand how to leverage the elements of the tools. No getting around that core understanding

Jakub Holý 2020-12-31T08:34:49.478800Z

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??

Gleb Posobin 2020-12-31T08:50:15.481900Z

It is applicable, but do I have to create a global resolver for every such list then?

Gleb Posobin 2020-12-31T08:50:48.482200Z

This seems weird, plus I now have to be conscious about naming it uniquely since if it appears somewhere else, pathom will resolve it.

Gleb Posobin 2020-12-31T08:52:12.483800Z

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).

Jakub Holý 2020-12-31T08:52:38.484600Z

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.

Gleb Posobin 2020-12-31T08:53:29.486Z

But this is also more general than pagination, this is required for any query that is nested and needs params.

Jakub Holý 2020-12-31T08:54:31.487Z

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?

Gleb Posobin 2020-12-31T08:54:55.487300Z

Yes.

Jakub Holý 2020-12-31T12:20:27.490600Z

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

tony.kay 2020-12-31T15:18:59.491200Z

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

tony.kay 2020-12-31T15:55:02.491500Z

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

tony.kay 2020-12-31T15:58:27.491700Z

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!.

tony.kay 2020-12-31T16:03:16.492Z

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.

tony.kay 2020-12-31T16:04:12.492200Z

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

tony.kay 2020-12-31T16:05:03.492400Z

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.

❤️ 1
Gleb Posobin 2020-12-31T20:17:54.495300Z

Thank you for the explanations!

Gleb Posobin 2020-12-31T20:25:06.495500Z

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.

Gleb Posobin 2020-12-31T22:29:11.495800Z

I thought maybe pre-merge would do that, but the posts in current-state are an empty vector for some reason.

tony.kay 2020-12-31T22:34:29.496Z

you probably don’t want load-field, since it assumes it already knows the target

tony.kay 2020-12-31T22:36:42.496200Z

targeting supports append on a to-many edge of to-many results

Gleb Posobin 2020-12-31T22:40:34.496400Z

But targeting needs the result to be of the form {:user/posts [post1 ... postN]} to be able to insert this somewhere?

Gleb Posobin 2020-12-31T22:41:16.496600Z

I know the target on the additional loads where I am using load-field, just capture this in the onclick handler of a button.

Gleb Posobin 2020-12-31T22:41:45.496800Z

I don't know the target on the first load, so using load! for it.

tony.kay 2020-12-31T22:41:51.497Z

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})

tony.kay 2020-12-31T22:42:11.497200Z

the posts resolver on the back-end has an input…is that :user/username?

Gleb Posobin 2020-12-31T22:42:53.497500Z

No, there are two resolvers: :user/username -> :user/id and :user/id -> :user/posts.

tony.kay 2020-12-31T22:43:34.497700Z

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

tony.kay 2020-12-31T22:45:43.497900Z

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.

tony.kay 2020-12-31T22:45:56.498100Z

in the latter case you’d just use a ns-qualified parameter for triggering that

tony.kay 2020-12-31T22:46:14.498300Z

:params {:pathom/context {:user/username "bob"}}

tony.kay 2020-12-31T22:46:18.498500Z

something like that

tony.kay 2020-12-31T22:47:11.498700Z

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

Gleb Posobin 2020-12-31T23:56:38.499200Z

Hmm, pathom/context seems to work only for idents.