This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-01-06
Channels
- # announcements (18)
- # asami (3)
- # aws (10)
- # babashka (47)
- # beginners (343)
- # calva (36)
- # cider (4)
- # clojure (66)
- # clojure-europe (9)
- # clojure-nl (3)
- # clojure-uk (23)
- # clojurescript (30)
- # community-development (69)
- # conjure (1)
- # eastwood (9)
- # events (7)
- # fulcro (81)
- # graalvm (1)
- # malli (5)
- # meander (1)
- # off-topic (41)
- # pathom (15)
- # rdf (1)
- # reitit (6)
- # sci (57)
- # shadow-cljs (18)
- # spacemacs (4)
- # startup-in-a-month (1)
- # testing (2)
- # vim (1)
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?
I don’t recall any issues with load-action in f2 that affected targeting, and cannot really see how that would be happening, sry
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
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.I caught the missing braces, sorry about that. But it is still failing to build.
Any dom elements like div span etc I put in there get an error that they cannot appear in tbody.
@U01HJ7S277X 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?)
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
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
At the repl, it returns the query that I expect.
(And thanks for the guidance!)
Yes, the nil went away. I don't see exactly why, but seems all good on that.
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.
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.
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.
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?
Also note: you should not be using row-query-inclusion for regular attributes you want on a row
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.
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
I have a question concerning attributes.
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?
You can do it either way, which is what ao/identities
is about.
(defattr price :entity/price :double
{ao/identities #{:item/id :option/id}})
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.
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.
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…
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.
not saying you can’t query, just that the queries are not automatically constrained to the domain of use.
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
My comments are not constrained to Fulcro, and were meant as general real-world data modeling advice when using name-spaced attributes.
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
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.[:lesson/course
{:lesson/course-lessons
[:lesson/id :lesson/title :lesson/description :course/title]}]
You should be able to query it along :lesson/id
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]}]
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!
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?
I suspect the former is sending the right query. It is then up to you to understand Pathom and get the correct response produced.
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.
@U01HJ7S277X
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?
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]}]}
> 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.
@U4VT24ZM3 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.
@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
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.
@U01HJ7S277X let me know if any changes to https://blog.jakubholy.net/2020/troubleshooting-fulcro/ would have made troubleshooting your problems easier 🙏
Thanks, @holyjak. I will certainly let you know. I'm definitely making use of it!
@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
@U01HJ7S277X 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.
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.
That makes more sense now
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!
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.)
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!!!
@genekim https://github.com/fulcrologic/fulcro-rad-demo/blob/master/src/shared/com/example/ui/invoice_forms.cljc#L75
Report startup is done here: https://github.com/fulcrologic/fulcro-rad/blob/develop/src/main/com/fulcrologic/rad/report.cljc#L459
https://github.com/fulcrologic/fulcro-rad/blob/develop/src/main/com/fulcrologic/rad/report.cljc#L423 https://github.com/fulcrologic/fulcro-rad/blob/develop/src/main/com/fulcrologic/rad/report.cljc#L439
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.
The loading is similarly very compact: https://github.com/fulcrologic/fulcro-rad/blob/develop/src/main/com/fulcrologic/rad/report.cljc#L153
@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
FYI you can use ro/controls
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
...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
...
@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!" 🙂 🤞🤞🤞
😂 Getting closer! Hoping to replicate what you've done in the demo soon, which would be amazing!
@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!!! 🎉🎉