Fork me on GitHub
#fulcro
<
2018-11-08
>
currentoor00:11:22

@claudiu looks pretty cool!

currentoor00:11:48

hopefully we can rip out our html5 routing code soon!

claudiu11:11:47

@currentoor Hope so. Trying to implement the things on the roadmap now and see just how much stuff I can put into the library out of the box(via configs), and still have it as a general library that is flexible enough to support different approaches. It's usable now, the code should not change much, but as I implement other stuff might change a few implementation details.

sooheon00:11:56

I’m not quite at the fulcro router docs yet, but I enjoyed the [URL as source of truth](https://keechma.com/guides/router/) idea in keechma. Can anyone familiar with both say how much daylight there is between the two? The main thing this pattern gets you is that you don’t end up 10 navigations into the UI with modals and whatever with URL staying the same. You can copy/paste URLs and as much application state as makes sense should come along, and back/forward “just works”.

claudiu14:11:15

@sooheon My personal preference for this is to have the intial-load logic all in one place. Ex: https://bit.ly/2QrKoo3 you have query, child components, ident + intial-load stuff. Makes it easy to reason about how it works and also manageable to add some more advanced conditionals dealing with not loading stuff that's in the app-state already.

claudiu14:11:39

That said, trying to build it so that it's flexible enough to allow for other flows. "The Controller Manager will start or stop controllers according to the route params" @U0CKQ19AQ This seems similar to the concepts you added in https://bit.ly/2SMShGj have that on the roadmap. Not exactly sure how to implement (middleware or utility-method), but the nav-router design will also have the option for this type of routing that splits the responsibilities with multiple "leaf-routers"(controllers-ish) and have those builtin.

tony.kay16:11:35

So, the pattern I’m suggesting does not use any kind of global URI parsing…just splitting the components into a vector. I.e. /admin/account/1/reports becomes ["admin" "account" "1" "reports"]. The routers compose, and each receives the “path not used so far”. So, the root router might consume “admin” and route to Admin, and then the Root router “signals” the next reachable child router with the rest of the path. So, in this example Admin itself is probably a router that consumes ["account" "1"] from the page and routes to the Account component (setting the ident to [:account/by-id 1] or something, and it then sends [“reports”] to the next “reachable” router. This way the path it auto-interpreted at the point of use, and everything composes.

tony.kay16:11:04

so then you don’t need to declare routes up front. They naturally form through composition, and self-heal in refactoring

tony.kay16:11:04

That demo code I wrote shows how to implement the router composition and signalling…and would be straightforward to extend to support dynamic code loading.

tony.kay16:11:40

So then, no matter what “history event” system or pattern you use from the UI, you can simply take the target path and hand it to the root router.

tony.kay16:11:15

Then, on refactoring, if you wanted to preserve paths (or even alias them for some reason) it is as easy as adding a fn that transforms an incoming path vector of strings into the new form…trivial. Everything is disconnected, composes well, and only code splitting logic need be mixed in (I think…possible that isn’t necessary)

tony.kay16:11:48

The “signalling” system that passes the remainder of the route along could also carry whatever metadata you wanted, and then each router could simply define behavior based on inputs…no need to “build that in”.

claudiu16:11:51

Ohh snap. :) Sounds like a novel idea. Is there a system like this in any other librarie/framework ?

tony.kay16:11:00

So, here’s where you have to “think differently” when it comes to Fulcro. Other systems don’t have the option. They don’t have components composed by queries and a normalized state database. That feature of Fulcro is the secret sauce

tony.kay16:11:45

The more you realize that, the less you’ll try to copy the “old ways”…it’s taken time for me to adapt as well…I keep having this “aha!” moment (3 years running now) at the implications.

tony.kay16:11:16

You have a query that is encoded with your graph…lots of really cool stuff falls out of that

claudiu16:11:29

Oky. But you find the leaf routers via the component query. So there still is a top level router that has to find the right component for the uri (and load the module if its not loaded) right ?

tony.kay16:11:30

and every element of that graph says “here’s where I live in the database”

tony.kay16:11:45

not a concern of a top-level

tony.kay16:11:54

any level could load something

tony.kay16:11:06

and thus you could have nested code splitting of the tree

tony.kay16:11:28

look, you’ve got a general “data load” that is composable, right?

tony.kay16:11:36

code splitting is no different

tony.kay16:11:01

the code loading command is not tied to any specific component

tony.kay16:11:25

and the dynamic queries mean that you just need a way to communicate the class of the loaded “sub-root” component once it’s loaded

claudiu16:11:34

:( but in youre code you have a component and read the quey and finds the leaf router components... But with dynamic query the component is empty as start, all the components are in multimethods/atom

tony.kay16:11:05

when you start you are not empty

tony.kay16:11:10

when you start you have a tree

tony.kay16:11:25

when you load you have a missing bit of data that has to be satisfied async

tony.kay16:11:36

so instead of multimethods, you do something different

tony.kay16:11:58

For example: code loading is something you should only do one at a time (not in parallel) for routing…which is an ok restriction

tony.kay16:11:21

so, make a global place to put a callback that the loaded things “just call blindly”…they don’t know what it does.

tony.kay16:11:39

so then code splitting is programmed to call this global, configurable (atom, say) lambda

tony.kay16:11:49

that that is where your router can put a set-query!

tony.kay16:11:20

a routing command that supports dynamic code loading could just “always” call the same function that basically does the code loaded check and calls the callback if it is, and registers it/loads the code if it isn’t.

tony.kay16:11:01

I’ll see if I can find some time to add code splitting to that demo…but if you read that stuff carefully, I think you can do something like it

claudiu16:11:11

Oky. Get its a bit more clearer now.

claudiu16:11:37

Only if yoy have free time. I get most of the idea now. :)

claudiu16:11:49

The thing is tha fulcro imo already has a bit of novelty. With the nav-router want to make the beginner experience better and familiarity I think is a important factor. Trying to figure out how to compose it best with what I have planned for the nav-router :)

claudiu16:11:50

Will draw up a flow, and share it :) think they could coexist nicely 😁

claudiu18:11:39

Going through what we discussed and the code a bit. Do you think about that routing solution as a "all or nothing". Or more like integrate it gradually into an app ?

claudiu18:11:16

what I'm thinking of is just having wildcard routes (the user picks the level) and in the on-before-enter you do nav-router/init-leafe-nodes Something like

(def routes
  {:main    [["/articles/*" :articles]
             ["*" :main]]})
             
(defsc-route Articles [this {:keys [db/id person]} computed]
  {:query          [...]
  :ident           [:page/articles :db/id]
  :initial-state   (..)
  :on-before-enter (fn [router-data]
                     (nav-router/init-leaf-routers router-data)) //init the router stuff

claudiu18:11:10

the nav-router can handle the code-splitting (defer route change until data loading or other stuff)... and the rest will dispatch to the page leaf-routers, that pick apart the data from the uri

claudiu18:11:28

@U0CKQ19AQ hope I got this right 😅 sory the ideas are registering so slow with me

tony.kay19:11:59

So, I’m torn….I completely agree that the beginner experience in all things clojure should be better. Fulcro included. However, I don’t want to necessarily “emulate” what the rest of the world is doing as the means of making that happen. If there’s an actual better way of doing it, then I want to do that…making something easy is about good documentation, templates, etc.

tony.kay19:11:47

I do not claim the the current routing solutin in Fulcro is ideal or easy. I always intended for things to develop around it that made it easier….not that plain defrouter is hard, actually…but the entire HTML5 thing does get a little worse

tony.kay19:11:08

But, giving users a “Rails-like” thing where they just turn a knob or two and “magic happens” isn’t necessarily the idea either, since that often leads to a monolithic thing that doesn’t scale well or respond to dynamic needs

tony.kay19:11:39

Library code is the hardest code you’ll ever write. Striking a good balance between ease and effectiveness is very very hard.

tony.kay19:11:32

I would not personally add a “router event” system.

tony.kay19:11:13

Async behaviors that you’re trying to get to thre almost always center around loads (or mutations with a response)…those systems already have “callback” capabilities that can trigger UI changes.

tony.kay19:11:16

but it’s your baby 🙂 I’m sure plenty of ppl will appreciate anything that makes it easy….just beware that easy can be the enemy of flexibility…and that can cause long-term pain and backlash.

claudiu19:11:57

yep. Trying to keep that in mind, but as you say, finding out now how hard it is to balance this stuff. Not really striving for easy, but now I think it's a bit to hard/all over the place. Not getting routing & loading part right is also painful in the long-run.

tony.kay20:11:16

yeah, this stuff is tuff….much harder than throwing together some business software

sooheon00:11:44

The following seems to be different: fulcro-nav-router (event -> state -> url): “The Router only listens browser back/foreword events (see http://html5doctor.com/history-api/#historical-events) the browser url changes are a side-effect of the router updates.” keechma (url change -> state): “Keechma’s router has no side-effects (these are set up when the application is started) and has no globally shared state. It is implemented in a purely functional way - it only transforms the data between formats.”

claudiu11:11:42

@sooheon Yep. Personally think this is a nicer pattern that is more flexible, added that one since I've seen the listening to router in pushy and js libraries. Not listening to html5 push, allows you to just update you're ui (with transact) and issue a url update that will not cause the router life-cycle methods to go full circle. But if you like that pattern better, nothing stopping you... just add :uri-routing-type :none and have the pushy set-page! do a router/nav-to! on every uri url change 🙂

claudiu12:11:54

Still working on the internal methods, afterwords will add scenarios like this with code examples to the docs.

eoliphant13:11:55

ah sweet, i'd been playing around with integrating reitit

claudiu14:11:37

@eoliphant what do you mean by integrating reitit ? it's already built in. The routes just need to have this format https://bit.ly/2PIJZR7

eoliphant14:11:39

ah, i meant that I was playing around with something similar, creating a routing lib for fulcro with reitit 🙂

claudiu14:11:08

agh 😄 cool 😄 To be honest will probably drop reitit in the future. Just used it now since I don't have time to write something for the minimal syntax that is needed for the route matching 😄 Don't want to spend my time doing that, and liked the route format better than bidi

tony.kay01:11:07

If anyone is watching the state machine progres…I’ve started a branch that has initial implementations of mutation and load support…not released yet (but usable via a deps git hash). Here are the docs: https://github.com/fulcrologic/fulcro-incubator/blob/feature/state-machine-io/state-machine-docs.adoc#mutations-and-state-machines

8
rainbow-mouth 4
eoliphant03:11:32

super quick question, not exactly clear on where/how to hook in pathom in the server code to replace the defquery stuff

claudiu05:11:55

@eoliphant what server are you running custom or easy-server ?

currentoor05:11:55

@eoliphant you just replace your current parser with a pathom based one, don’t use easy server

nbdam09:11:09

How would you implement generic select or autocomplete widget? One which would have props sometihing like: - props :select/list :select/id :select/title :select/target { :select/list [:root/people] ; source list for options :select/id :db/id ; option value prop :select/title :person/name ; option prop :select/target [:group/by-id 1 :group/activeperson] ; db destination, param for transacted mutation } Since queries seem to be static, and can't evaluate props it seems I would need to generate many select components, one for each combination of params (perhaps with macro..)

claudiu09:11:16

Also autocomplete & dropdowns in book demos http://book.fulcrologic.com/#_demos

nbdam10:11:24

@claudiu thanks, so basically the idea is to put the state in database and 'evalute' props from there by using idents for lookup data... makes sense, need to get used to it since i seem to forget it sometimes...

claudiu11:11:33

@nbdam Yep. If you have a crud use-case, you could just add the options as a list in the parent + the selected id and have a simple display component, that just renders the data & calls the on-click callback that is sent from the parent (transact should use the this of the parent for refresh to be ok)

4
claudiu11:11:42

@sooheon Yep. Personally think this is a nicer pattern that is more flexible, added that one since I've seen the listening to router in pushy and js libraries. Not listening to html5 push, allows you to just update you're ui (with transact) and issue a url update that will not cause the router life-cycle methods to go full circle. But if you like that pattern better, nothing stopping you... just add :uri-routing-type :none and have the pushy set-page! do a router/nav-to! on every uri url change 🙂

eoliphant11:11:29

Thanks @claudiu @currentoor. Running easy server now, but in the process of ionizing the whole thing anyway

claudiu12:11:22

@eoliphant In easy server you can add other routes. Could just add '/api2' that has pathom process the request body and put that as a different remote in you're app. http://book.fulcrologic.com/#_adding_non_api_routes That way you can migrate one defquery at a time.

8
currentoor17:11:09

oh right that’s an even easier approach

eoliphant20:11:28

also, seems like (make-fulcro-server :parser my-pathom-parser) should work with easy-server?

nbdam12:11:13

Yep, this is to avoid repetition in the simple crud case. I was trying to specify everything as a few data props which would be paths in database to read/modify instead of using sub-components (eg. for options.) I've been thinking about it.. I don't think that approach plays well with fulcro's static :query composition/reading and re-rendering. The macro for generating many components might be the best solution to avoid boilerplate in case i have dozens of trivial select components that are basically all the same, with only difference in input list and mutation target.

claudiu12:11:38

@nbdam don't really understand why you say that fulcro static does not play well. So you're scenario is that you want a generic simple dropdown component right ?

claudiu12:11:55

something like :

(defsc GenericDropdown [this {:keys [id values-list selected-value]} {:keys [on-click]}]
  {:query [id values-list selected-value]
   :ident [:dropdown/by-id :id] 
   :initial-state (fn [parent-data] {:id (:id parent-data) ...})}
  (dom/ul
    (map <li> values list) ; create <li> etc...
  ))
  
(def ui-selector (prim/factory GenericDropdown))
  
(defsc DropDownPlaceholder [this {:keys [selector-x]} {:keys [on-click]}]
  {:query [{:selector-x (prim/get-query GenericDropdown)]
   :ident [:page/by-id :id]
   :initial-state (fn [x] {:selector-x {:id :article-type :values-list [:a :b :c] :selected-value 0}})}
   (ui-selector selector-x {:on-click (fn [x-id] (prim/transact! `[(ops/update-selected {:selected-value ~x}))])})
  )

nbdam13:11:58

I ment that query is static, it is not a function of params Though it might work with dynamic query.. i will look into that

nbdam13:11:49

I will look into your example later to see if this is what i wanted..

claudiu14:11:15

@nbdam fulcro is pretty cool, have a few options (incl dynamic query). Just one thing that I sometimes also forget is that there is no magic & not all components have to have indent/query :) the query might be static but you can pass lists/hashmap for a query prop :)

tony.kay16:11:40

A thing I commonly do is make a stateless dropdown component and send things to it. Of late, I’ve just been using semantic UI React’s dropdown in controlled mode, so I just treat it like an “controlled input”, instead of a first-class stateful component.

tony.kay16:11:08

i.e. no query/ident/initial-state…

tony.kay16:11:39

controlling a dropdown “externally” like that fits just fine in apps and is a lot less boilerplate/temptation to use a macro

nbdam17:11:56

DropdownPlaceholder + referencing list data from other enitities is akin to what I was asking about macro generation... Before I made query-less widget (pass in direct props) for testing - parent widget needs to query list data - I gues I am ok with it - passing onChange callback and making callback transaction in parent context has quite long boilerplate - otherwise I like it - it seems simple, and that is why I am lerning fulcro - for promise of simpler design - since Tony says he likes it it is large plus - he has a lot of experience with this Thanks for your input..:)

tony.kay17:11:31

“- passing onChange callback and making callback transaction in parent context has quite long boilerplate” Not sure what you mean by long boilerplate…

tony.kay17:11:04

you don’t even have to use computed if the component doesn’t have a query

tony.kay17:11:34

(ui-dropdown {:options options :value field :onChange (fn [v] (m/set-value! this :field v))})

tony.kay17:11:00

make the dropdown you write do nice things like call onChange with the correctly-typed value and it is pretty non-noisy and clean

nbdam05:11:07

transact! everywhere in form looked quite noisy, moving it to helper functions and passing in component as param cleans it up nicely