In fulcro/statecharts is there any particular issue with having side effects in a script and not return any ops? for instance why should one use fop/invoke-remote instead of comp/transact! to call a mutation? if there is ok-event/remote code defined in the cljs side of the mutation that would have to be rewritten into the statechart. are we losing some performance, predicatability doing so?
The point is that a fulcro mutation is a combination of local data effects and remote behavior. The local data effects are something you might want to model in the state chart itself, but that leaves you without any way to do the remote side. Thus the additional operation. Of course it's all meant to be composable, so you are welcome to do whatever you want. The thing is, you're probably going to write a mutation that you then need to send the state chart, so as it does things it can send events back to the state chart. Of the state chart is to localize it so you can reason about it. The intention is for you to not need mutations or transact directly at all in the state chart
yeah I think we are mainly asking ourselves these questions when trying to interact with other features of fulcro/rad (uism/forms/reports), as they have functions that are a transaction underneath, and it's often not easy without rewriting a bunch of the underlying code to achieve a statechart version of the local effects behind them an example when we try to use save-as-form, we even have a mutation that calls back to the statechart to simulate an ok-event:
(comp/transact!
app
[(form/save-as-form
{:root-ident root-ident
:delta memberships})
(bus/send-to-mutation
{:target group-details-statechart-session
:event :event/group-updated})])
I assume a clean solution would be to have a util namespace with a statechart friendly version of uism/form/report core functions (in part as well because uism features are all behind transactions),My longer-term goal is to write statecharts for forms and reports so those can be invocations or sub-charts. As invocations certainly the parent/child/siblings could signal each other. Invocations are nice in that your “parent” is a known thing, so sending events in that direction is trivial. Etc. Right now the fact that you can mix/match means it’s relatively easy to port. I worked on the routing more this week, and I hope to finish those changes soon…
> ExecutionModel is used in contexts where the data model may need to be updated. In those contexts > one may call the methods on the data model directly, or the execution model may allow a return value of the > expression to act as the data model change.
> As invocations certainly the parent/child/siblings
That's an interesting feature, right now we haven't really figured out how to properly use invoke
but now I'm looking at a giant statechart and I'm thinking whole parts could be statecharts of specific child components. so I need something that stiches together the invoke/the rendering of the child/passign events between them without relying on known session-id
I'm looking into rstate/istate now to see if I get it now and if it fits what I have in mind
and ui-routes
The idea is that the statechart would dynamically control the query of the parent, and you render with ui-current-subroute
co-locate a statechart on a component, and invocations will start that chart as the state is entered, and end it on exit of the state
etc
@tony.kay yes looks like com.fulcrologic.statecharts.integration.fulcro.ui-routes might do what I want, I'm working on a poc turning a giant statechart for a multistep wizard using that. Currently the top component is a bunch of cond and nested cond on the statechart confg deciding which step/substep to render
So at the moment I’m refactoring routing so that Fulcro itself has a top-level protocol, refactoring RAD to use that, so that there is portability and statecharts can be the routing system as an option. That will get the entire ecosystem in line with the capabilities I want
for this poc I only want component level routing
@tony.kay why does uir/routes impose a limitation to have a constant ident in the :root?
Here is a very simple demo showing general ideas, including support for the parallel statechart node: https://github.com/fulcrologic/fulcro-with-statecharts/blob/simple-demo/src/main/com/example/client.cljs Not sure that runs with released libs…I’m using dev branches right now.
The constant ident is so that it can find the component to change the query, and also find it in app state so it can update the reference key to point at the proper child ident
I’m still working on the design.
I guess I am perverting the concept a bit 😄 I wanted to be able to break down the routing tree between the components with istate
Constraints:
• You own the application root, so I don’t know how you composed in the stuff you want to route. BUT, I need to know where it lives as a component, and in app state
◦ Constant ident makes that easier
◦ Otherwise I have to have some kind of initialization story that matches with that you’re doing in your app.
At some point the uir code just needs to know where it is controlling the query. I do NOT want to rely on React hooks, since Fulcro is not tied to React. So use-route can be a supporting lib function, but not a core requirement.
one of the things I'm trying to do is like RouteA2 in your exemple except the subroutes of RouteA2 would be defined in its own statechart
every defrouter in dynamic routing has a constant (generated) ident, for example
I’ve got an invocation example here somewhere…
found it…just a sec
the routing states are still all in the same statecharts
Yes
I’ve not gotten to arbitrary routing composition that way. Invocations are structurally disconnected, and therefore the idea of a “current route” get super tricky if you allow invocations (of which there could be many at a time) trying to contribute….esp since the charts now have to coordinate. I mean, I have the child/parent session IDs, so I know the relations…
I guess the question boils down to one of “how much do you want URL integration”? 😄
yeah in that case it's to structure the navigation between a tree of steps within a modal, so I don't care about URLs at all
purely about routing and queries
I don’t think there is actually a limitation on using the routing states and system in a sub-chart…I haven’t tried it, but there isn’t, per-se, any explicit limitation
the only limitation is that when you create a set of ui routes in a chart, it has to include a root that has a constant ident
mmmm…maybe there is
I think I do currently have the assumption that the routing system is global…but we could associate it with the session ID of the chart instead (if I didn’t). Then it would be composable, but routing commands would be specific to charts, and composition would not be very good for any kind of global routing (I want to go there, and it happens to be in an invocation of an invocation of my root chart)
and most people on the web do care about the URL unfortunately
I guess it could go by path composition. A URL path starts at the top chart, and “consumes” as much of the URL as it can…then passes the remainder to any sub-chart as an invocation parameter.
My current implementation’s “simplification” is to say that all items in the routing system have constant idents…but that’s not really a limitation, because I’m just having you compose things with known idents for routing purposes, and any of those “constant ident’ things can act as a “root” for any kind of dynamism that you control within your chart (e.g. you make your own join, on-entry handler, etc.)
this is all alpha/experimental. I know there’s a set of optimal patterns here
I guess it would be really simple (ignoring URL) to have the invocations work like this: • You declare on the target component when your ident isn’t constant (we can auto-detect, but not reliably) • If a non-constant ident is detected/set, then the parent route waits to receive a :event/child-set-ident event from the invoked chart back to the parent Now you are simply responsible in your statechart startup to make sure you send that event, at which point the parent can properly render the child.
In general we can support this idea of :event/child-set-ident to the parent as a way to notify the parent that the child’s ident changed at any time, and that can auto-heal the join in the parent component’s state.
istates works with :com.fulcrologic.statecharts.integration.fulcro.ui-routes/statechart but use-statechart checks the :statechart option is the difference on purpose?
meh…
no
I was trying to localize options like I do in RAD so they are self-documenting. The hook was really a one-off that can document the one and only key it wants, but you’re right that there’s no reason for them to land in different spots on a component
actually, I could argue that use-statechart should NOT use a component option, but instead an inpput arg to the function…there’s no reason why you couldn’t use multiple charts in a single UI component.
this is what happens in library design when you don’t have the time to keep working on it in a continuous block 😄
I just kind of coded this up (as pseudocode):
(defsc RouteA21 [this {:thing/keys [id value]}]
{:query [:thing/id
:thing/value
[::sc/session-id '_]]
:ident :thing/id
ro/statechart (statechart {}
(state {:id :top}
(on-entry {}
(script-fn [env & _]
(let [id (tempid/tempid)
my-ident [:thing/id id]
thing {:thing/id id
:thing/value 42}]
(merge/merge-component! app RouteA21 thing)
(scf/send! env (senv/parent-session-id env)
:event/child-ident-changed {:ident my-ident})
nil)))
and it occurs to me that what you’re going to be doing here is either (as in RAD) a load, or a merge component. I already had added a
ro/initialize :once ; :always or :never
option for auto-init, but perhaps just requiring the child chart to do an explicit initialization makes sense…Hm.
The routes state wrapper in UIR has no explicit requirement on not being nested. Its purpose is to auto-emit transitions to/from all immediate child routes to support direct routing requests, and to handle route denial (one could argue that this is perhaps a global concern, and not local, so that is a possible hang up).
The only real limitation of it is that each one has to have a unique ID…and the supporting helper functions might not work.
But there is the side limitation of “notify the user” if a request to move from one place to another is restricted. Of course you could hand-code that everywhere, but that’s a mess. The idea that a user cannot move to some location because of “unsaved” changes is kind of a weird case: It’s a local concern in that the local on-screen thing is what is messing with the request, but it’s a global concern in that requests to change positions in a webapp come from a global concern of the user.
Hm. perhaps statecharts already has a good answer for this. There IS no global concern on denial. A wildcard transition in a route could “capture” routing events on a :cond , subverting the top-level routing.
We still have the issue of URL synchronization (bookmark loading, user pressing forward/back in the browser, and updating the URL properly on state changes). The “external event” concern (bookmark, back/forward) is the harder one. The only complications on the URL tracking are trivial EXCEPT in those “external source of route” cases.