I love re-frame! It's what has helped me write programs that do what I want them to do, and am able to build upon them for years without them collapsing like a house of cards, as has been my normal experience.
However, I'm finding for one project, I have what feels like a sprawling events.cljs
file, and a flow of events that's difficult to keep track of and keep in my head.
I wrote this program to help document the re-frame events. I'm ridiculously proud of myself for writing a Clojure program to read the events.cljs
file and extract the "dispatch/call graph," pulling out the re-frame/dispatch
calls and the reg-event-fx
dispatch maps. (Holy cow. This is my first visceral experience of awe at the value of homoiconicity!)
This is a work in process —- no guarantees that it will work with anything besides the included file. :)
I was playing around with rendering the call graph with graphviz (did not have good results), or maybe vega arc-diagrams, but I'm finding the text display surprisingly compact and useful.
Each vector is the name of the event, followed by all the other events that it dispatches.
([:graph/load-initial-state-success :graph/server-load-hotkeys]
[:graph/select-board :graph/save-and-clear-searchbox]
[:graph/select-board :graph/load-board-lists]
[:graph/select-board :graph/select-board-next-leftpane-state]
[:graph/callback-load-board-lists :graph/load-list-cards]
[:graph/callback-load-board-lists :graph/load-list-card-counts]
[:graph/load-list-cards :graph/reset-card]
[:graph/load-list-cards :graph/generate-materialized-cards]
[:graph/reload-all-lists-and-cards :graph/load-list-cards]
[:graph/reload-all-lists-and-cards :graph/load-board-lists]
[:graph/reload-all-lists-and-cards :graph/load-list-card-counts]
[:graph/change-list-sort-mode :graph/generate-materialized-cards]
[:graph/generate-materialized-cards :graph/iphone-materialize-view]
[:graph/load-card-comments-attachments :graph/reset-card]
[:graph/load-card-comments-attachments :graph/load-card-comments]
[:graph/load-card-comments-attachments :graph/load-card-attachments]
[:graph/iphone-materialize-view :graph/iphone-load-card-attachments]
:
:
:
(count *1)
=> 65
Here's what this exercise helped me learn for an SPA which I use for Trello card management, which I've built up over 3 years — it's actually two SPAs, one intended for use in desktop browser (supporting keyboard accelerators) and the other in mobile interface .
-
I've learned that I have at least 65 events calling other events, putatively making the remainder leaf events, which don't dispatch any other events.
-
Even before this exercise, it occurred to me that I don't know what the ideal event flow and shape of the call graph should even look like! What does one look like that allows for re-use, versus what feels like spaghetti right now? Are there naming schemes for the events to support better organization?
-
And what should the naming scheme be for events, to impose an order/organization to it?
- user-load-lists
- op-focus-on-comment-box
-
This has helped me zero in on where the complexity/messiness is — not surprisingly, it typically is around operations that have multiple asynchronous steps.Like this one:
- [:graph/archive-card :graph/next-card]
- [:graph/callback-archive-card :graph/generate-materialized-cards]
- [:graph/callback-archive-card :graph/load-card-comments-attachments]
- [:graph/callback-archive-card :graph/load-list-card-counts]
-
I.e., to archive a card, it’s got to advance the view to the next card, call the backed to archive it, upon the callback, pessimistically update the list of card, load the next card contents/comments… I’m wondering aloud how better to do these multi-step sequence of events, where it’s more obvious what the steps are — documenting this way is good, but steps seem currently splattered across too many events, which linkages not obvious enough.
Run in the REPL. Input file is hardcoded in main.clj.
(in-ns 'graph)
(def infile "/Users/genekim/src.local/trello-workflow/src/cljs/trello_workflow/events.cljs")
(def events (->> (m/gen-events infile)))
(count graph)
; 110 forms
([:graph/load-initial-state-success :graph/server-load-hotkeys]
[:graph/select-board :graph/save-and-clear-searchbox]
[:graph/select-board :graph/load-board-lists]
:
:
:)
(def save-graph (create-graph events))
(u/viz-graph save-graph {:layout :dot :rankdir :LR
:save {:filename "save.dot"
:format :dot}})
(u/viz-graph save-graph {:layout :dot :rankdir :LR
:save {:filename "save.png"
:format :png}}))
This sample file that works is in the resources directory.
There are many.
- The way I pull out the
reg-event-fx
dispatch maps is a hack -- I grab either the last s-expr inside thefn
or the enclosedlet
. I don't parse it at all, so if there are variables in there, they're not evaluated/expanded, or anything like that.
Pull requests welcome.
- write a proper command-line interface to pass in input file as parameter (so you don't have to write it in a REPL)
- in graph, put back in the event parameters, so you can see the shape of data used as parameters to event
- parse
:http-xhrio
fx
To use the graphviz visualation of graph, you need to install it as per: https://graphviz.org/download/
Mac
- brew install graphviz