Fork me on GitHub
#fulcro
<
2022-01-09
>
Piotr Roterski07:01:42

hello 👋 Is it possible to reuse defsc-form definition data across different forms? Let's say I have two similar versions of a form:

(form/defsc-form
  PostForm [this props]
  {fo/id post/id
   fo/title "New Post"
   fo/attributes [post/body]})

(form/defsc-form
  PostReplyForm [this props]
  {fo/id post/id
   fo/title "Reply"
   fo/attributes [post/body]})
Their definition is just data, so, in theory, I should be able to share it so the common parts are reused and the differences are immediately visible. I tried doing something like this:
(def post-form-data
  {fo/id post/id
   fo/title "New Post"
   fo/attributes [post/body]})

(form/defsc-form
  PostForm [this props]
  post-form-data)

(form/defsc-form
  PostReplyForm [this props]
  (-> post-form-data
      (assoc fo/title "Reply")))
but it doesn't even compile and I'm not well-versed enough in cljc macro semantics to fix it. Is there another syntax that could work or would it require patching the defsc-form macro to make it work (if that's even possible)?

Jakub Holý (HolyJak)15:01:26

Error checking in the macro is little dumb in this regard. The solution I can think of is to make something like the merge fn but make it a macro (that just calls merge) - that way it is also evaluted at compile time and resolves into a {...} before the code inside the defsc sees it.

tony.kay17:01:36

Yeah sorry, the macro has to be able to read the options at compile time, so general expressions are just not compatible. I could have it resolve symbols but that would make failures even more mysterious. It is possible I can fix that. If so I'll give it a shot sometime soon

tony.kay17:01:37

So, the defsc-report macro definitely needs it to be a compile-time map, because it needs to know several things in order to emit the correct code. Forms, technically, at the moment, could allow an expression, but I’m not sure it makes sense to loosen that since then it would be inconsistent with report.

1
tony.kay17:01:12

This is similar to defsc, which requires it be a map so it can do compile-time checking of various arguments.

tony.kay17:01:43

You can always copy the macro code to your own code and loosen these restrictions. It’s a trivial change in the case of form.

Piotr Roterski18:01:56

thanks for the insights 🙌 I was kinda hoping for a magic solution so it's easy to fully benefit from data-driven rad's nature without loosing the restrictions. I feel a bit out of my depth playing with those macros, but maybe I'll come up with something that works for me.

tony.kay18:01:28

So, the change in the form macro is easy

tony.kay18:01:55

(if (map? options) (existing expression) options) and then the macro spec can have :options any?

tony.kay18:01:41

specifically, when you copy the spec for the macro, change: line 306 to: :options any? and when you copy the function that processes the macro, line 493:

options      (if (map? options) 
                          (opts/macro-optimize-options env options #{::subforms ::validation-messages ::field-styles} {})
                          options) 

👍 1
tony.kay18:01:13

The remaining work would just be fixing namespaces

tony.kay18:01:32

My intention at some point is to have function versions of these that are pure user-space (not compile-time)…form is very amenable to that, but not sure about report. It’s on the TODO list for this year.

fulcro 1
❤️ 1
tony.kay18:01:04

You should only need to copy those three things I think (spec for macro args (used to parse the args), defn defsc-form*, and the macro itself), and refer to everything else from form ns.

tony.kay18:01:19

so it’s only like 50 lines

tony.kay18:01:16

actually, there’s more. The form ns DOES do macro arg checking in other fns 😕

tony.kay18:01:06

yeah…the query building requires being able to look at macro args at compile time

tony.kay18:01:47

there just no way in the present code form to use expressions reliable, unless you want to deal with the complexities of resolve and evaluation at compile time, which is a HUGE can of worms that is usually not profitable

tony.kay18:01:00

When I wrote this stuff originally I wanted good compile-time errors, but giving those errors requires the map be readable by the macros. The function versions (once I write them) won’t give compile errors (of course), but it isn’t as easy as I said above

Piotr Roterski18:01:25

> My intention at some point is to have function versions of these that are pure user-space (not compile-time) I'd much appreciate that. While it's not something I necessarily need, I think it'd be beneficial in terms of usability (reuse & visible diffs across versions). I'm trying your suggestions right now, but, long term, having to maintain my own macro/lib version might not be justified.

Piotr Roterski18:01:08

I just made those two changes on lines 306 and 493 in my local fulcro-rad's fork (to skip all the ns hassle) and it actually worked!

tony.kay18:01:22

hm…I’m actually surprised because of that other function…but ok! 😄

😅 1
Jakub Holý (HolyJak)19:01:17

Wouldn't it be simpler to make the macro version of merge?

tony.kay20:01:59

there’s really no such thing..you’d have to resolve the data, which means all sorts of trickery

Jakub Holý (HolyJak)20:01:53

Ah. I thought that if had a macro that merges two maps at compile time, I would get a map. I guess I have to study resolution rules.

tony.kay20:01:35

I’m a bit unclear myself…it’s always something I have to tinker with because I don’t try to cross this boundary, because it is a pain, but imagine you’re compiling a CLJS file

tony.kay20:01:36

then it is VERY clear: that def simply will NEVER exist where the macro expands

tony.kay20:01:04

thus the complication: you’d HAVE to use CLJC files for that to work at all, and then it would be generally confusing about what the exceptoins are, etc.

tony.kay20:01:27

and things like declare followed by a later def would be a problem, etc, etc

tony.kay20:01:47

it’s madness to allow expressions in things that a macro is treating like syntax

tony.kay20:01:21

But, I could provide a functional version that doesn’t do much checking, and that would be usable in this case

tony.kay21:01:16

Give me a few…I’m seeing how hard that is…so far it looks pretty easy

tony.kay23:01:17

@UHA0AQZ2M I got functional versions of the macros working and released. See release announcement.

tony.kay23:01:38

I tried them out in place of the macros in the demo and they seemed to work. If you find any problems with them I’d appreciate a report. See the source. Really wasn’t much to it, but it does cause a loss of error checking. Also, the macros will tolerate the use of full attributes in some places as keys, and will rewrite them to keywords. The new functions do NOT do that…so for example the report form-links options allows this, but in the function version you MUST use keywords as the keys of form-links. Other options are similarly affected (if they accept an attribute as a key, they most likely will require a keyword in the function form)

Piotr Roterski07:01:32

awesome, thank you ! :medal:

Piotr Roterski08:01:43

I've tried it and it seems to be working in my app so far. If I see any problems down the road, I'll definitely report them

zeitstein17:01:38

I'm trying to use Raw without React. The build fails with:

Build failure:
The required JS dependency "react" is not available, it was required by "cljsjs/react.cljs".

Dependency Trace:
        app/ui.cljs
        app/app.cljs
        com/fulcrologic/fulcro/raw/application.cljc
        com/fulcrologic/fulcro/algorithms/indexing.cljc
        com/fulcrologic/fulcro/mutations.cljc
        com/fulcrologic/fulcro/algorithms/merge.cljc
        com/fulcrologic/fulcro/components.cljc
        cljsjs/react.cljs

tony.kay17:01:36

You cannot use the components ns... Looks like merge is?? Just need to change that to use raw

tony.kay17:01:14

Possible oversight on my part. Not sure why merge cannot use raw. I'll check

zeitstein17:01:40

Thanks, Tony. Double-checked with developers guide. But as you can see, I'm just requiring raw.application.

tony.kay18:01:43

yeah, I have a few refs to the wrong ns internally…I’ll fix those and release a patch

gratitude 2
tony.kay18:01:11

The transaction processing ns also uses component…might take a bit more work.

tony.kay18:01:15

Give me a few hrs

zeitstein18:01:19

Not urgent at all, Tony.

tony.kay19:01:17

Try 3.5.10-SNAPSHOT

tony.kay19:01:17

There are a few nses that need non-raw (all of the routers, and the normalized-state helpers), but nothing critical. I had to add refresh-component! to the list of functions you have to supply. It is only internally used by synchronous transactions, so if you’re using RAW and sync, you’ll need to implement that for your rendering

zeitstein20:01:13

It works 🙂 Thanks for the heads-up about refresh-component!.

tony.kay18:01:04

Fulcro RAD 1.1.0 released to Clojars Version 1.1's main change is an upgrade of an external js dependency that was a little rough: js-joda. The date-time formatting was manually rewritten to be CLJC to use browser Intl instead of having to have the (rather large) js-joda localization file. This means the internationalized date formatting has a MUCH smaller build footprint, but IF you use localized date PARSING that requires a locale (e.g. isn’t just numbers), AND you are using the RAD data-time namespace to build a DateTimeFormatter, then you need to switch your code to use cljc.java-time directly instead.

❤️ 1
tony.kay23:01:32

RAD 1.1.1 just released as a follow-on due to user requested feature: There are now non-macro functions to create reports and forms (report/report, and form/form). These functions take the same options map as the macros, and allow an optional render body function. The main difference is that they generate the RAD form/report at runtime. This also allows you to use expressions for the options maps.

tony.kay23:01:02

So:

(defsc-form MyForm [this props]
  {fo/id blah/id
   ...})
can be written as
(def MyForm
  (form/form ::MyForm
    {fo/id blah/id
     ...}))
but more importantly the options map in the second case can be an arbitrary expression.

🚀 2
tony.kay23:01:11

Caution: The macros do some error checking and magic for you that is lost when using the functions. In particular the macros try to let you use attributes in certain map keys (e.g. ro/form-links {thing/nm Form}). This is fixed up by the MACRO ONLY, and will not work in the function version. You must instead use they keyword (i.e. ro/form-links {:thing/nm Form}). Contributions welcome if anyone wants to port over the error checks and key morphing.