fulcro

Book: http://book.fulcrologic.com, Community Resources: https://fulcro-community.github.io/, RAD book at http://book.fulcrologic.com/RAD.html
tony.kay 2021-01-06T00:00:13.084900Z

I consider that function deprecated in F3. Just use df/load! from the action section of the mutation in F3. For F2, I’m not aware of such an issue…you sure your post-mutation is right?

exit2 2021-01-06T15:24:07.114100Z

No worries, I’ll try and debug. It almost has more to do with the pristine state flashing in other fields while I’m typing onChange in one field

exit2 2021-01-06T15:24:21.114300Z

m/set-string, the props are coming in as the previous state or something..

exit2 2021-01-06T15:40:23.116100Z

if I pass those other fields as computed it works, but that doesn’t seem ideal

Christopher Genovese 2021-01-06T01:00:10.085500Z

I start with a relatively standard report giving table of data (title, description, course for each of a series of lessons), and I want to change that to a split table, where each course is a subheading and there is a subtable beneath it with lesson title and description for each lesson in that course. I have a working resolver which returns gives me the data in proper form (`:lesson/lessons-by-course`) with shape:

[{:lesson/lessons-by-course
                    [:lesson/course
                     {:lesson/course-lessons
                      [:lesson/id :lesson/title :lesson/description]}]}]
Before even getting the sub-table to work, however, I try a simple Row Item component just to see that I am getting the data. As follows (similar to Sec 6 in the RAD book):
(defsc GroupedLessonSubtable [this {:lesson/keys [course course-lessons] :as props}]
  {:query [:lesson/course
          {:lesson/course-lessons
           [:lesson/id :lesson/title :lesson/description]}]
  :ident :lesson/course}
  (div
   (h2 (str "Course: " course))
   (p (apply str (interpose " " (map :lesson/id course-lessons))))))

(defsc-report GroupedLessonList [this props]
  {ro/route               "grouped-lessons"
   ro/title               "All Lessons by Course"
   ro/source-attribute    :lesson/lessons-by-course
   ro/BodyItem            GroupedLessonSubtable
   ro/row-pk              lesson/course
   ro/row-query-inclusion [:course/id :course/title]
   ro/columns             [course/title]
   ro/column-headings     {:course/title "Course Title"}
   ro/run-on-mount?       true
   ::report/paginate?     true})
But the app fails to build at all, giving me `Error: Invalid join, {:ui/current-rows nil}'. If I remove the query and ident in the BodyItem class, the message is the same. Without the BodyItem option, I get a table of course titles. (Note: I have a resolver that maps :lesson/course -> :course/id and :course/title successfully when this problem is not happening.) Q1. Any idea what is wrong with this? Q2. In general, what is the RAD best practice for this sort of structuring of data. I'd just like the standard table for each subgroup with a subheader, and I have the data in exactly the needed form. It seems that this should be easy, but I've spent a fair bit of time on this simple thing with no success. I'd very much appreciate some guidance. Thank you.

Christopher Genovese 2021-01-06T14:17:26.101900Z

At the repl, it returns the query that I expect.

Christopher Genovese 2021-01-06T14:17:34.102100Z

(And thanks for the guidance!)

tony.kay 2021-01-06T14:47:57.112800Z

Did you get past the nil then?

Christopher Genovese 2021-01-06T23:14:21.153100Z

Yes, the nil went away. I don't see exactly why, but seems all good on that.

Christopher Genovese 2021-01-06T01:18:12.085600Z

I caught the missing braces, sorry about that. But it is still failing to build.

Christopher Genovese 2021-01-06T01:25:07.085800Z

Any dom elements like div span etc I put in there get an error that they cannot appear in tbody.

Christopher Genovese 2021-01-06T01:53:26.086500Z

To follow up, Q2 is my main question, but more generally, is it possible to nest a report within a report or other component, and how can those reports work with the props provided beyond specifying report options. For instance, in my "split" table (one table per group with group name -- course -- as a subheading), I'd just like a standard report table within each course (the grouping variable). If I put a Report class within the BodyItem class, how do I access and do anything with the props while using the power of the built-in report.

exit2 2021-01-06T02:04:25.086600Z

yes its fc2

tony.kay 2021-01-06T03:02:22.086800Z

I don’t recall any issues with load-action in f2 that affected targeting, and cannot really see how that would be happening, sry

tony.kay 2021-01-06T03:04:27.087Z

@genovese one problem is that the default report wrapper is a table, and it is illegal to put a div in there. You have to switch to a list style (ro/layout-style, I think?)

tony.kay 2021-01-06T03:05:48.087200Z

on the join error, I’m not seeing a reason for that error, The :ui/current-rows is an internal bit of the generated query. OH…but that query has to be able to get the query of your row component

tony.kay 2021-01-06T03:07:20.087400Z

You have to have a query and ident on the row, but unless the GLS class isn’t building, that query should not be nil

tony.kay 2021-01-06T03:07:43.087800Z

what happens on a REPL if you (get-query GroupedLessonSubtable) ?

tony.kay 2021-01-06T03:08:04.088Z

the error you’re getting indicates that that is returning nil for some reason.

tony.kay 2021-01-06T03:09:28.089300Z

So, the rendering plugin is not currently very customizable, but you can escape from it by just providing a render body. (in the defsc-report). The data is there, the load logic is written, etc.

tony.kay 2021-01-06T03:11:57.091900Z

So, the answer to Q2 is one of: 1. Customize the row, as you’re doing, but you may have to switch render styles (the default is table, and list is supported). If you use table, then your element (because of DOM, not Fulcro) must be a table row. If you use list it is more general. NOTE: This is all dependent on the internals of the particular render plugin you use. 2. Write the render body of the defsc-report yourself. All the logic is there, there are helpers in report.cljc for triggering all the logic. Render it exactly the way you want. 3. For a special repeated pattern, it might be work making your own render plugin that you can then reuse across your app.

tony.kay 2021-01-06T03:15:19.092800Z

The demo has a working version of using the list style (in comments) with comments:

tony.kay 2021-01-06T03:16:02.093800Z

If you continue to get a nil in your query it could be a regression. I have some vague memory of recent work on query inclusions, perhaps that broke something?

tony.kay 2021-01-06T03:16:45.094200Z

Also note: you should not be using row-query-inclusion for regular attributes you want on a row

tony.kay 2021-01-06T03:17:44.095300Z

that is for extra stuff that doesn’t have an attribute declaration, and is typically used for really extreme circumstances like you want to have some nested join on each row to get some data. That said, when you provide a BodyItem then that BodyItem has the row query.

tony.kay 2021-01-06T03:18:20.096Z

I’m not sure R.Q.E. is even ever meant to be usable with BodyItem, because that is the row…yeah, they are never meant to be used together

Toni Tuominen 2021-01-06T13:27:57.097600Z

I have a question concerning attributes.

Toni Tuominen 2021-01-06T13:33:35.101800Z

Say I have an an item entity and an option entity. An item can be customized based on options. Both the item and the options have a price associated with them. I should probably have a generic price attribute that gets associated with both entities. My problem is that it feels funny that I need to declare in the price attribute all the entities that it can go on. I feel like that is against the whole idea of the entity-attribute-value system. Because I have to declare the possible entities for the attribute it seems that I could just as well have :item/price and :option/price instead of say :price/amount. Any thoughts?

Christopher Genovese 2021-01-06T14:31:06.109800Z

Thanks, @tony.kay. Ah, so I should have used table row elements explicitly for 1, but 2 is more general. I tried a version of that using TableRowLayout (?) from semantic-ui, but I will reconsider that. I want a table rather than list style but was trying something simple, to understand why the former wasn't working. I misunderstood the purpose of the query inclusions it seems, so I'll eliminate that. (I wanted to use information from a join that should be in the pathom context (lesson/course -> course/title), but I admit that I continue to be less than perfectly clear on how to make the components get just the information they need beyond the global resolvers.) I really appreciate your help and time on this. Thanks again

tony.kay 2021-01-06T14:37:15.110400Z

You can do it either way, which is what ao/identities is about.

(defattr price :entity/price :double 
 {ao/identities #{:item/id :option/id}})

tony.kay 2021-01-06T14:38:48.110800Z

be careful about over-generalization, though. If there are ever time when you might want to seamlessly merge something that has an option price with something that has an item price (they’re just maps), then you lose info, because you’ve dropped the specificity.

tony.kay 2021-01-06T14:39:54.111Z

if anything, I tend to find even :item/price isn’t specific enough. I end up needing :inventory/list-price, :line-item/price, etc…prices are not static in time, and must often be saved in different contexts.

tony.kay 2021-01-06T14:41:23.111400Z

e.g. I show you the list price, but apply a discount, and put that on an order as a line item price, then later in time I adjust my list price (which should not affect historical line items on invoices). Having specific names for things is pretty important. Generalizing something that has a true single meaning (e.g. :gov.irs/tax-id-number ) that you mean to put on “people” or “companies” might be ok, but then again SSN and EIN have different formats…so…

tony.kay 2021-01-06T14:46:22.111900Z

Then consider query power. If you overgeneralize price then you can no longer reason over “price” in the database as easily. “What is the average price of the items in my inventory” is trivial if the fact is named :inventory/list-price, but more complex (and potentially less performant) if you have to search for :entity/price (which is on many many things) and narrow it down to those that that live on :inventory/id entities.

tony.kay 2021-01-06T14:47:24.112600Z

not saying you can’t query, just that the queries are not automatically constrained to the domain of use.

Toni Tuominen 2021-01-06T14:49:13.113Z

Ok thanks a ton. Seems like the generic attributes would work better if I worked in plain datomic and datalog. I sort of lose the benefits of the automatic stuff in fulcro if I use more generic attributes

Christopher Genovese 2021-01-06T15:32:20.115800Z

That cleared up a lot, @tony.kay, and it's looking good already. (I'll take your advice on 3.) I hope you don't mind, one more follow up. Let's say I am doing a BodyItem for the rows in my last question, where each row has shape

[:lesson/course
     {:lesson/course-lessons
      [:lesson/id :lesson/title :lesson/description]}]
If I use that shape as the query of the row component, I get the right info in the component code. But I'd also like that component to have access to :course/title which is directly accessible from the database through :lesson/id (and which I have a resolver for). How best to get access to that additional information in the component code? I originally thought that I could just add it to the query and it would be resolved (since there is a :lesson/id -> :course/title resolver), but that doesn't seem to work. So do I have to probe the db explicitly? What other options are there? I hope this Q is clear. Thanks.

tony.kay 2021-01-06T15:33:06.115900Z

None of those comments had anything to do with Fulcro

Björn Ebbinghaus 2021-01-06T16:29:26.116300Z

[:lesson/course
  {:lesson/course-lessons
   [:lesson/id :lesson/title :lesson/description :course/title]}]
You should be able to query it along :lesson/id

➕ 1
Björn Ebbinghaus 2021-01-06T16:40:58.116600Z

I have to say, I don’t quite understand your data schema. Shouldn’t it be something like this?

[:course/id
 :course/title
 {:course/lessons [:lesson/id :lesson/title :lesson/description]}]

Christopher Genovese 2021-01-06T18:05:47.116900Z

I had tried that but the title did not resolve (though in other contexts is did). I'll look at it more closely. What about the schema looks problematic? This is getting a unit of data that looks like a {:lesson/course an-id :lesson/courses [...{lesson-details}]} where lesson details has id, title, and description properties for lessons associated with that course. (The lesson/course is the same as course/id but a "foreign key" in the original lessons data.). Please do let me know if there's something inappropriate here for that purpose. Thanks!

tony.kay 2021-01-06T18:41:53.117100Z

My comments are not constrained to Fulcro, and were meant as general real-world data modeling advice when using name-spaced attributes.

genekim 2021-01-06T19:51:27.120700Z

Before I post my question, here’s something amazing that Jakub helped me get running — it’s a RAD form running as a child of F3 stateful component. My jaw hit the desk when we finally got it running — it’s marvelous being able to use the Form machinery (Save, Undo, Cancel buttons)!! For those of you who want to replicate this, here’s the link to relevant part of source: https://github.com/realgenekim/fulcro-rad-demo/blob/gene-experiments/src/shared/com/example/ui/session_forms.cljc#L125 (TL;DR: required creating a new router, just for the SessionDetailsManual component…) So many aha moments learning how routers work, and @holyjak walking through how routing works. It really is true what @tony.kay says: “it’s just data.” It borders on mind-blowing to me to what extent this is true, and how it benefits the developer!

genekim 2021-01-06T19:55:25.124700Z

Okay, here’s my question — I’m trying to get a report running, very similar to the Fulcro RAD demo where you can see “all invoices for Customer 101.” In the demo, he does this by creating a RAD report, which queries :account/invoices, which calls a resolver with a query parameter of an :account/id. I tried to replicate this: in the Conference Report, I want to click on a conference, which will pull up all the YouTubePlaylists, via a query parameter :conference/uuid. But my problem is that the query parameter disappears, during the Load transaction of the YouTubePlaylist report— see screenshot from Fulcro Inspector > Transactions. What am I doing wrong that is causing that query parameter to disappear? Thx!! (I’ll post the equivalent for that Customer 101 report in just a second, to compare/contrast.)

genekim 2021-01-06T19:58:37.125800Z

In contrast, here’s the Transactions for the Fulcro RAD example for “Show Invoices for Customer 101”. (Ah, I changed it to Customer 102 while trying to prove I understood what was going on… 🙂. THANK YOU!!!

tony.kay 2021-01-06T20:23:20.128300Z

reports send control values as parameters

tony.kay 2021-01-06T20:25:01.129100Z

The route sets the given control data as part of route params

tony.kay 2021-01-06T20:26:59.130200Z

just follow the source. Reports are not that complicated inside.

tony.kay 2021-01-06T20:29:12.131400Z

lines 280-390 is the state machine, which is the vast majority of the inner workings. ~110 lines of code. most of which has to do with sorting, filtering, and pagination. Ignore those bits and it isn’t very much code at all.

tony.kay 2021-01-06T20:29:57.131900Z

The loading is similarly very compact: https://github.com/fulcrologic/fulcro-rad/blob/develop/src/main/com/fulcrologic/rad/report.cljc#L153

tony.kay 2021-01-06T20:31:02.132100Z

Use Inspect. It is very clear in network tab what query is being sent, and what response you get. That will divide your problem space in two: Is Fulcro sending the right query? Is server giving expected response?

tony.kay 2021-01-06T20:31:36.132300Z

I suspect the former is sending the right query. It is then up to you to understand Pathom and get the correct response produced.

tony.kay 2021-01-06T20:32:32.132500Z

There is an EQL tab in Inspect where you can manually run the query to take Fulcro out of the picture. See “Send to” button in network tab, where it will send the query to the EQL tab so you can run it over and over again without having to fiddle with UI.

Björn Ebbinghaus 2021-01-06T20:32:36.132700Z

@genovese Not problematic or inappropriate in any way, I don’t know your usecase. But usually a join connects two different entities. Here you have a join between a lesson and a course {:course/lessons [:lesson/id]} or {:lesson/course [:course/id]} (There is no explicit many-to-one, many-to-many, one-to-many join. The only thing that gets close to this is the usage of the plural oder singular for the attribute name) The namespace of the key (`:lesson/`) is the “type” of the entity and the key part (`/id`) is the attribute. So you would only have to join two entities of the same type if there is some sort of recursive relationship like: [:task/id {:task/subtask [:task/id]}] You know what I mean?

Björn Ebbinghaus 2021-01-06T20:37:13.133400Z

For example:

(defresolver lesson-resolver [_ {id :lessen/id}]
  {::pc/input #{:lesson/id}
   ::pc/output [:lesson/title :lesson/description {:lesson/course [:course/id]}]}
  ; implementation
)

(defresolver course-resolver [_ {id :course/id}]
  {::pc/input #{:course/id}
   ::pc/output [:course/title]}
  ; implementation
)
This would answer the query:
{[:lesson/id 42] ; or where ever you get the id from
 [:lesson/title
  {:lesson/course [:course/title]}]}

tony.kay 2021-01-06T20:38:45.133600Z

> How best to get access to that additional information in the component code? So, from a Fulcro perspective, when you want data in some particular place, just ask for it…add the prop where you want it. But Fulcro cannot magically write your server-side query or data initialization to actually make it appear there. Fulcro is very (intentionally) literal. There is no magic on the front end in Fulcro or RAD as far as this question goes. If the data is in your graph db on the front-end, it will be in the rendered component (data + query + ui tree all literally match). Fulcro is about making it easy to initialize (initial state), normalize (via query/ident), denormalize (query + normalized state), and “patch” (via load, data targeting, merge-component) that model…but the normalize/denormalize via UI query is quite literally a marshall/unmarshall kind of thing…like base64 encode/decode. The answer, then, is that you must understand Pathom, and how to teach it to respond to the data needs you have in your EQL queries. This is a very simple process. See pathom connect documentation for how to properly write your resolvers.

tony.kay 2021-01-06T20:42:31.133900Z

Another way of saying all that is “you get access to it by putting it there” 😄

Christopher Genovese 2021-01-06T21:13:09.134700Z

@mroerni I think I see what you mean. Here I have two entities courses and lessons with a one to many relation. Within lessons there is a foreign key to the course so course/id and lesson/course point to the same thing. I see the joins as both a statement of shape and as a connection here, and perhaps in that I am not getting the full value from it. Your resolver specs make sense; I'm getting toward that but less elegantly. Thank you.

Christopher Genovese 2021-01-06T21:16:29.136Z

@tony.kay That's very helpful. So am I correct in assuming that anything I want in the props I must put in the query even if the pieces come from different parts of the database? I think I was mapping the queries too literally in my mind to the shape of the response. Thanks again

genekim 2021-01-06T21:17:26.137Z

@tony.kay — super helpful. I'm retracing exactly those steps in my code, and I think I've copied everything exactly... 1) The YouTubePlayList Report accepts the :conference/uuid` as a parameter: https://github.com/realgenekim/fulcro-rad-demo/blob/gene-experiments/src/shared/com/example/ui/youtube_playlist_forms.cljc#L51

genekim 2021-01-06T21:18:24.137700Z

2) The (rroute/route-to!) happens here, upon an onClick in the report row: https://github.com/realgenekim/fulcro-rad-demo/blob/gene-experiments/src/shared/com/example/ui/conference_form.cljc#L58

genekim 2021-01-06T21:22:12.139900Z

...I don't manually call (load-report!) or (start-report!) , but I don't think you do either in the "Invoices for Customer 101" example. Trying to puzzle out where I'm losing that route-param...

genekim 2021-01-06T21:34:59.141400Z

@tony.kay Hang on a second. Finding some expected things when I did a git pull. Things not wired as I represented above. Hoping in ten minutes, I'll be able to announce, "You were right!" 🙂 🤞🤞🤞

tony.kay 2021-01-06T21:41:57.141500Z

Correct. The component queries (w/ident) are about norm/denorm. It is not some fancy SQL query engine or datalog. It literally pulls what you put. The only way to get data in props is 1. to query for it, and 2. make sure the db matches the shape of the query so you get it.

tony.kay 2021-01-06T21:43:15.142200Z

it works in the demo…

genekim 2021-01-06T21:50:08.143500Z

😂 Getting closer! Hoping to replicate what you've done in the demo soon, which would be amazing!

genekim 2021-01-06T22:05:10.149Z

@tony.kay WOOHOO! It's working! You were right! 🙂 The problem: well, I think I got a very confused after trying so many different things last night. But going through your flow, it showed that I had disabled the ro/controls, and the query parameters didn't line up. Here's what's amazing to me. I fixed the query parameters so it showed up in the load in transactions, but it returned an empty set of rows. I copied the query into the EQL Explorer to confirm it returned empty, went back to my CLJ REPL of the query that worked last night, and confirmed that worked. Looked at the difference, and saw I was passing the wrong query-param. What I'm finding so dazzling about Fulcro is how much you can do without the UI — confirming that the data is right thru the REPL is truly amazing, and is definitely changing how I think about UI work. Thanks for the help, and accommodating this interruption in your day!!! 🎉🎉

❤️ 2
Jakub Holý 2021-01-06T22:24:56.149200Z

@genovese let me know if any changes to https://blog.jakubholy.net/2020/troubleshooting-fulcro/ would have made troubleshooting your problems easier 🙏

Christopher Genovese 2021-01-06T22:57:42.149600Z

Thanks, @holyjak. I will certainly let you know. I'm definitely making use of it!

❤️ 1
Jakub Holý 2021-01-06T23:04:07.149900Z

FYI you can use ro/controls

Christopher Genovese 2021-01-06T23:04:09.150100Z

@tony.kay Given that connection between query and props, what can you do beyond the source attribute for reports (via defsc-report)? Regarding your ealier point, I have been using inspect and logging server responses, and I think you are right that the issue is in understanding the way to use pathom. The simple cases like those often documented are clear enough, but I'll work on it more deeply. Thanks

tony.kay 2021-01-06T23:11:14.151600Z

@genovese remember that any query has to have a root. The source-attribute is that root. From there you have rows. That is what the columns (or BodyItem) are for. That is the source of the row query. If you look at the macro it literally builds a component for you so you don’t have to supply BodyItem.

tony.kay 2021-01-06T23:12:27.152Z

and to do that, it needs to know what to put in query…but the “I’ll do it for you” part is intentionally simple. I don’t want 10,000 knobs. I don’t want to write that mess, and it has proven to be difficult to get right by everyone who’s ever attempted it. The better approach IMO is what I’m doing: giving you a few sane easy (super common) things, and then plenty of escape hatches.

Christopher Genovese 2021-01-06T23:13:07.152300Z

That makes more sense now

tony.kay 2021-01-06T23:14:27.153300Z

Yeah, this is not “Visual XYZ” 😄

Christopher Genovese 2021-01-06T23:14:42.153500Z

🙂