Fork me on GitHub
#hyperfiddle
<
2023-05-25
>
jmckitrick13:05:26

I’m trying to internalize the use of new in electric. It’s not quite clicking with me when it should be used, and why, if everything in clojure is ostensibly about functions, it’s not just a function call?

2
Dustin Getz13:05:05

for the same reason reagent uses square brackets. we could have chosen square brackets.

jmckitrick13:05:13

I remember seeing that in the docs, but this isn’t a compenent, right?

Dustin Getz13:05:13

clojure is dynamically typed so there is no robust way to distinguish between reactive fn call and regular function call, and since Electric prioritizes interop with regular clojure, you need a way to indicate the call convention

jmckitrick13:05:15

db (new (db/latest-db> user/!xtdb))

Dustin Getz13:05:32

ah that is the second usage of new, which is to await a missionary flow

jmckitrick13:05:09

Sounds like I need to do a little reading on missionary

Dustin Getz13:05:43

awaiting a missionary flow is like awaiting a promise, except instead of a promise to 1 async future value, a flow is a sequence (possibly infinite) of async future values

jmckitrick13:05:23

That’s the streams and signals part, right?

Dustin Getz13:05:57

yes, streams and signals are kinds of flows

xificurC13:05:58

you only need to learn missionary if you're going to write integrations at the edge of the system (e.g. bridging a database to electric, a third-party js API that returns promises, ...). Electric largely abstracts missionary away. If you need help integrating you can ask us here and we'll see if we can help

jmckitrick13:05:19

cool, thanks.

jmckitrick13:05:08

We’re currently working on an in-house app for sprint planning poker, so we can get a feel for it

👍 2
jjttjj14:05:46

I'm trying to render markdown text in electric. I'm using https://github.com/nextjournal/markdown which works in cljc and I'm not really picky if the parsing occurs on the client or server. I've tried the below approaches and a few others but not having any luck. At a high level is there an obvious approach to this? Is just parsing the markdown to an html string and setting innerHTML of the dom/node the way to go here? (Edit: I think for the markdown case here it probably is good to just use innerHTML or maybe just use a js library to handle all the parsing after the text hits the dom, but I am sort of curious about the way in electric to think about a transformation of data to nodes)

(defmacro electric-markdown1 [text]
  (let [data (md/parse text)]
    (walk/postwalk
      (fn [x]
        (if (vector? x)
          (let [[tag & content] x]
            (case tag
              :<> `(dom/text ~@content)
              (list* (symbol "hyperfiddle.electric-dom2" (name tag)) content)))
          x))
      (md.transform/->hiccup data))))
;; Can't take value of macro 

(e/defn hiccup-to-dom
   [hiccup-data]
   (let [tag      (name (first hiccup-data))
         children (if (map? (second hiccup-data))
                    (drop 2 hiccup-data)
                    (drop 1 hiccup-data))]
     (println "tag" tag)
     (dom/element tag
       (map (fn [child]
              (if (string? child)
                (dom/text child)
                (hiccup-to-dom child)))
         children))))

(e/defn electric-markdown3 [text]
  (let [data (md/parse text)
        hiccup (md.transform/->hiccup data)]
    (hiccup-to-dom. hiccup)))
;;; stack overflow during compilation

2
xificurC15:05:51

• I don't see any usage of electric-markdown1 • in hiccup-to-dom you have to use e/for instead of map on the children. map is clojure land so you cannot call electric code like dom/text in it • electric-markdown3 looks alright except it's calling the broken hiccup-to-dom

xificurC15:05:05

re data to nodes, that sounds like an interpreter, which hiccup-to-dom is

xificurC15:05:20

the advantage of feeding data into electric instead of a raw string is you'll get fine-grained reactive DOM updates as the interpreter walks the data. Unchanged data nodes, provided they are properly keyed, will leave the DOM untouched

xificurC15:05:08

there's also an overhead to running electric, so it also might be that setting innerHTML will be faster

jjttjj15:05:35

> • electric-markdown3 looks alright except it's calling the broken hiccup-to-dom Trying this give me:

(e/defn hiccup-to-dom [hiccup-data]
   (let [tag      (name (first hiccup-data))
         children (if (map? (second hiccup-data))
                    (drop 2 hiccup-data)
                    (drop 1 hiccup-data))]
     (dom/element tag
       (e/for [child children]
         (if (string? child)
           (dom/text child)
           (hiccup-to-dom. child))
         children))))

(e/defn electric-markdown3 [text]
  (let [data (md/parse text)
        hiccup (md.transform/->hiccup data)]
    (hiccup-to-dom. hiccup)))

...
(e/client
  (electric-markdown3. "#hi"))
Encountered error when macroexpanding hyperfiddle.electric/boot.
Failed to analyse form
{:in [(map? (second hiccup-data))]}
` > Our tutorial app has a working example Thanks yea I have the innerHTML method working fine (edit: edited this code a bit to add a . to the recursive hiccup-to-dom call. still getting compilation errors, will report back if something is meaningfully different/promising though)

jjttjj15:05:02

I was just looking at that! I think that's getting me closer

jjttjj16:05:00

Ok yeah I think this works, using code from dom2/element. Thanks for the help!

(e/def h2e)
(e/defn electric-markdown3 [text]
  (let [data   (md/parse text)
        hiccup (md.transform/->hiccup data)]
    (binding [h2e (e/fn [hiccup-data]
                    (let [tag      (first hiccup-data)
                          children (if (map? (second hiccup-data))
                                     (drop 2 hiccup-data)
                                     (drop 1 hiccup-data))]
                      (dom/with (dom/new-node dom/node (name tag))
                        ;; hack: speed up streamy unmount by removing from layout first
                        ;; it also feels faster visually
                        (e/on-unmount #(set! (.. node -style -display) "none")) ; hack
                        (e/for [child children]
                          (if (string? child)
                            (dom/text child)
                            (h2e. child))))))]
      (h2e. hiccup))))

Dustin Getz16:05:09

fwiw the streamy unmount lag might be fixed in the next version, we have a pretty good idea of what is happening, it was a mistake in how unmounts are synchronized over network

👍 4
jjttjj17:05:30

Here's a bit of a refactored version if anyone's interested in the future. supports :div.classes.like.this and optional attriburtes, but not :div#id yet. (had enough of yak shaving on this for today)

(e/def H2e)
(e/defn ElectricMarkdown [text]
  (let [data   (md/parse text)
        hiccup (md.transform/->hiccup data)]
    (binding [H2e (e/fn [hiccup-data]
                    (let [[tag & tagclasses] (str/split (name (first hiccup-data)) #"\.")
                          [children attrs] (if (map? (second hiccup-data))
                                             [(second hiccup-data) (drop 2 hiccup-data)]
                                             [(drop 1 hiccup-data)])
                          attrs (cond-> attrs
                                  (seq tagclasses) (update :class concat tagclasses))]
                      (dom/with (dom/new-node dom/node tag)
                        (when (seq attrs)
                          (dom/props attrs))
                        (e/on-unmount #(set! (.. dom/node -style -display) "none")) ; hack
                        (e/for [child children]
                          (if (string? child)
                            (dom/text child)
                            (H2e. child))))))]
      (H2e. hiccup))))

(comment
  (ElectricMarkdown. "# Hello"))

👀 4
xificurC18:05:23

You could write a transformer for that library, skip the middleman

💡 2