Fork me on GitHub
#fulcro
<
2020-05-21
>
tony.kay05:05:16

RAD update: I just push new versions of most of the RAD libraries and a new version of the demo is on the master of that repo. I’ve officially added containers (finally) which (at the moment) can hold reports. Non-local controls are gathered to the top, and local controls (a new concept) stay with the nested items. For fun I also added some victory charts into one of the reports just to show how you can doll up a report without having to write any of the surrounding logic (just the integration for the chart). Of course, it is simple enough to turn that into a plugin charting library, but for now I just hand coded up an example, which also shows how you can escape the auto-rendering and take control of some pretty fancy stuff. Just this as the body of the report:

;; Use the report data to render a couple of victory charts with the table and controls
  (let [{:keys [group-by]} parameters
        bar-width (case group-by
                    :day 5
                    :month 10
                    20)]
    (dom/div :.ui.container.grid
      (dom/div :.row
        (dom/div :.six.wide.column
          (dom/div :.ui.grid
            (dom/div :.row
              (ui-victory-chart {:domainPadding {:x 50}}
                (ui-victory-bar {:data     current-rows
                                 :labels   (fn [v] (comp/isoget-in v ["datum" "items-sold"]))
                                 :barWidth bar-width
                                 :x        "date-groups"
                                 :y        "items-sold"})))
            (dom/div :.row
              (ui-victory-chart {:domainPadding {:x 50}}
                (ui-victory-bar {:data           current-rows
                                 :barWidth       bar-width
                                 :labels         (fn [v] (math/numeric->currency-str (comp/isoget-in v ["datum" "gross-sales"])))
                                 :labelComponent (ui-victory-tooltip {})
                                 :x              "date-groups"
                                 :y              (fn [datum]
                                                   (math/numeric->double (comp/isoget datum "gross-sales")))})))))
        (dom/div :.ten.wide.column
          ;; The auto-rendered table
          (report/render-layout this)))))

❤️ 24
aw_yeah 8
tony.kay05:05:47

where I’m adjusting the bar width according to the groupings, the charts stay in sync with the report, etc. Note that the table and all controls come from just the auto-render (`render-layout`).

tony.kay05:05:40

Then containers let you pile it all into a single screen…this dashboard:

(defsc-container Dashboard [this props]
  {co/children         {:sales     sales/RealSalesReport
                        :customers account-forms/AccountList
                        :inventory item-forms/InventoryReport}
   co/layout           [[{:id :sales :width 16}]
                        [{:id :inventory :width 8} {:id :customers :width 8}]]
   co/route            "dashboard"
   co/title            "Dashboard"
   copt/controls       {::refresh {:type   :button
                                   :label  "Refresh"
                                   :action (fn [container] (control/run! container))}}
   copt/control-layout {:action-buttons [::refresh]
                        ;; these inputs are pulled up from nested reports (any control that is not marked local will be)
                        :inputs         [[:start-date :end-date]]}})
Turns into this:

tony.kay05:05:31

Where: • All parameters, pagination, current page, sort orders (of every report), date ranges are ALL saved in the URL. So you can bookmark an exact view • The From/To are pulled up from the sales report. You could actaully embed two sales reports, and each would share the date range, but could be shown with different groupings or pivot options (again saved in HTML5 route) • The two charts are sharing the same table data as the sales table. They’re just 3 renderings of the same controlled data.

tony.kay05:05:22

I can’t guarantee that everything is perfectly API stable (the newest features often run into some design flaw as I flesh them out), but at this point I am trying to evolve the library without any breaking changes. I’m still marking it alpha for now, but I’d say it is relatively safe to use.

tony.kay05:05:18

there are some missing bits that really should be in place (but aren’t): • React error boundaries • More guardrails coverage • More error checks and good error messages for inputs • The implementation of the UI plugins are still messy. These are likely to get cleaned up, so if you’re making a UI plugin I may still cause breakage there…sorry 🙂 • More documented vars for config keys so it is easier to use. I’ve got quite a few of those done, but docs are never done. The experience when you misconfigure something right now is far from great, since React burns everything down if there is an exception. That’s probably the place where user experience can be most improved: just making the error messages good.

tony.kay06:05:05

Still, I’m quite happy with how it is coming along. The core architecture of co-located query/ident/state mechanism with normalization is giving us a lot of wins here. Just some examples: We get fan-in fan-out of trees (auto-normalization). The query is also a form of introspection. We use it for things like discoverable routes (no need to manually create an maintain some kind of routing primitives) and to figure out when forms have sub-forms. Things like picker options (to choose an account, for example) can be normalized and cached to use the same data in forms and reports without reloading them. Normalization ensures that an edit in any form will be reflected in any cached report. Normalization allows us to give report controls names that will “unify” in dashboards, where they “just work” as pulled up controls from sub-reports because the reports would have all normalized them to the same location. The form state support over normalized data gives us a way to cleanly represent minimal diffs for form saves that are easy to adapt to any database (the Datomic adapter can save anything using roughly 200 lines of code).

👍 36
🔥 28
🤙 16
aw_yeah 8
kszabo08:05:00

absolutely stunning work! waiting for the next large project so that I can start out with this

zilti10:05:44

Time to move to Firefox... (okay, I don't even know if Firefox has such a feature)

thosmos23:05:40

It doesn’t. One of the Firefox devs replied to the twitter stream and said they’re looking into solutions

grischoun14:05:16

I have a question/issue regarding mutations. The context is that I am trying to access a REST api through Fulcro and Pathom directly from the frontend. To do so, I have defined a new remote as follows: (defn rest-remote [parser] {:transmit! (fn [_ {::txn/keys [ast result-handler]}] (let [edn (eql/ast->query ast)] (go (try (result-handler {:transaction edn :body (<?maybe (parser {} edn)) :status-code 200}) (catch :default e (js/console.error "Pathom remote error:" e) (result-handler {:transaction edn :body e :status-code 500}))))))}) And in the same .cljs file I have defined both the Fulcro and Pathom mutations as such: (defmutation update-datoms [{:datoms/keys [datom]}] (action [{:keys [state]}] ;; ... (rest-remote [env] (eql/query->ast1 [(transact-datoms {:datoms/my-datom [2]})])))` (pc/defmutation transact-datoms [env {:datoms/keys [my-datom]}] {::pc/sym transact-datoms` ::pc/params [:datoms/my-datom] ::pc/output [:datoms/id]} (log/info (type my-datom)));; Type of my-datom is a function!? My problem is that when I pass a non primitive value like the array ‘[2]’ to the ‘transact-datoms’ call, as shown in the example above, the Pathom mutation does not receive ‘[2]’ but a function!? (That’s the type of the variable when I print it out.) But when I pass a primitive value like ‘2’, it works as expected. Could anyone tell me what is going on?

holyjak16:05:46

The cljs data structures look like functions to Javascript (just look at the props of a Fulcro component using the React Dev Tools Components view. Could it be it?

holyjak17:05:01

Try coll? instead of type

grischoun18:05:47

@U0522TWDA coll? returns false. And thanks for the advice about React Dev Tools. It made me see that the system considers my-datomnot as a vector (in the Pathom mutation) but as a var. I.e., this is what I see in React Dev tools about it: my-datom: app.dashboard.mutations.datoms/my-datom

grischoun18:05:45

But I still don’t understand why.

holyjak19:05:10

Perhaps you meant you get the symbol instead of the value? Put ~ in front of it in the transaction vector. That is just how the syntax quote works https://8thlight.com/blog/colin-jones/2012/05/22/quoting-without-confusion.html

grischoun19:05:29

Right, quoting issue. It is much better now. Thank you very much for your inputs.

Eric Ihli15:05:17

I'm reading the app/math.cljc file from this video https://www.youtube.com/watch?v=euwuLyzMDVg&amp;list=PLVi9lDx-4C_T7jkihlQflyqGqU4xVtsfi&amp;index=10 and don't understand something.

#?(:cljs
   (do
     (defn- big-eq [x y]
       (.eq (n->big x) (n->big y)))

     (defn- big-lt [x y]
       (.lt (n->big x) (n->big y)))

     (defn- big-lte [x y]
       (.lte (n->big x) (n->big y)))

     (defn- big-gt [x y]
       (.gt (n->big x) (n->big y)))

     (defn- big-gte [x y]
       (.gte (n->big x) (n->big y)))))

(defn compare-fn
  #?(:cljs ([big-fn]
            (fn [x y & more]
              (if (big-fn x y)
                (if (next more)
                  (recur y (first more) (next more))
                  (if (first more)
                    (big-fn y (first more))
                    true))
                false)))
     :clj  ([core-fn]
            (fn [& numbers]
              (apply core-fn (map n->big numbers))))))

(def = (compare-fn #?(:cljs big-eq :clj clojure.core/=)))
(def < (compare-fn #?(:cljs big-lt :clj clojure.core/<)))
(def <= (compare-fn #?(:cljs big-lte :clj clojure.core/<=)))
(def > (compare-fn #?(:cljs big-gt :clj clojure.core/>)))
(def >= (compare-fn #?(:cljs big-gte :clj clojure.core/>=)))
How are the cljs and clj versions different? Under what circumstances would using the clj form in ClojureScript cause an error? I tested this out in my REPL by using the same form, the clj form, for ClojureScript and evaluating things like (< 3 5 8) worked just fine. But I'm too new to Clojure(Script) to be confident that there isn't a reason for the difference other than "artifact of iterations of refactoring".

tony.kay16:05:02

The cljs version uses a js library that can only compare pairs of big.js numbers

tony.kay16:05:15

and cljs < doesn’t understand bigdecimal numbers

tony.kay16:05:20

whereas the clj one does

tony.kay16:05:48

The whole point is to make them work the same for bigdecimals…they’re already fine for plain numbers

🙏 4
Eric Ihli17:05:12

Thanks! I still didn't get it. I threw (< (Big. 2) (Big. 3) (Big. 4)) in the repl and it still appeared to work. I was thinking since the clj/cljs logic was in big-lt/`core/<` and since we were passing that into compare-fn then compare-fn didn't need to handle any branching. Then it hit me... (< (Big. 2) (Big. 3) (Big 1))... Now I understand.

JAtkins23:05:49

What causes a fulcro app to not connect to inspect? I have have an app running in chrome and vivaldi that suddenly stopped connecting without any obvious errors.

tvaughan00:05:42

For me it was blocking third-party cookies

dvingo23:05:04

if you open the devtools for inspect itself there may be an error there

JAtkins23:05:59

Somehow that solves it? Not sure what is going on, but inspect is back for now.

dvingo00:05:26

i've had it crash (and the screen goes blank white) and then recover, so that may be it

dvingo00:05:06

the logs may only be captured once it's open so you may just want to leave the console open for a bit in case an error shows up in the logs

JAtkins00:05:26

Somehow my app state got replaced with a uuid, and that is crashing both the app and inspect. Good times 🙂

JAtkins01:05:49

3 hours later, I found out that I had accidentily moved a (transact! ... to the end of a swap, instead of the result of state.