Fork me on GitHub
#hyperfiddle
<
2023-07-08
>
Reut Sharabani15:07:45

How can I invoke some callback after an element is actually loaded? I'm trying to use mermaid to render some content and it requires calling (.run js/mermaid (clj->js {:querySelector ".mermaid"})) after the mermaid class has content (which is fetched from the server). I wonder how I can know when the content was actually generated since there are no elements that support onload in dom/* . I tried stuff like:

(when (not (s/blank? mmd))
  (try
    (dom/div (dom/props {:class "mermaid"})
             (println "rendering" mmd)
             (dom/text mmd))
    (finally (.run js/mermaid (clj->js {:querySelector ".mermaid"}))))) 
Or:
(when (not (s/blank? mmd))
  (dom/div (dom/props {:class "mermaid"})
           (println "rendering" mmd)
           (dom/text mmd)
           (.run js/mermaid (clj->js {:querySelector ".mermaid"}))))
But it seems run (which returns a promise) still happens before the content is actually loaded.

Dustin Getz15:07:24

When you say "it [content] is actually loaded" what exactly are you referring to as "content"

Reut Sharabani16:07:29

the class should exist and should contain the mmd. I should have mentioned mmd is a reference (?) it is defined as a watch (?) because it is communicated from the server. To give the overall perspective I have mmd files (mermaid files) on the server and I want to display them on the client. The client cannot download them directly via links, so I'm using a reference (atom + watch) by looking at examples. This is the definiton of mmd:

(e/server
    (let [!mmd (atom "")
          mmd (e/watch !mmd)]

Reut Sharabani16:07:46

(mmd is just a string)

Dustin Getz16:07:22

(let [x (e/server "foo")] (case x (js/mermaid.run ...)))

Dustin Getz16:07:15

this is an idiom we currently use that exploits the fact that the conditional node cannot evaluate it's body until after the remove value is available, i.e. if x is pending the control flow is blocked until we can test it

Dustin Getz16:07:49

(here we are using the default branch)

Reut Sharabani16:07:00

I'll try and see what you mean

Reut Sharabani16:07:09

oh I think I get it actually, sec

Dustin Getz16:07:05

It would be better if you could write (js/mermaid.run (e/server ...)), where the Electric evaluation rules will delay the .run until the parameter is available

Reut Sharabani16:07:08

isn't my when (in the original message) doing exactly that though?

Reut Sharabani16:07:50

the mermaid/run method receives selector (among other things) to render the matching elements. It does not receive mermaid strings.

Dustin Getz16:07:52

Can you post a minimal (complete) example that runs

Reut Sharabani16:07:16

let me edit it to be minimal, it can take a while

Dustin Getz16:07:15

oh, i think what's happening is that mmd starts "" and then later receives a value? in this case js/mermaid.run does not have a dependency on mmd and therefore Electric will not re-evaluate it if it has already ran

Dustin Getz16:07:03

It is idiomatic to wrap it like (defn mermaid! [_mmd] ...) to force the dependency explicitly

👍 2
Reut Sharabani16:07:38

(e/defn Observer []
  (e/server
    (let [!mmd (atom "flowchart LR\n\nA-->B")
          mmd (e/watch !mmd)]
      (e/client
        (.initialize js/mermaid {:startOnLoad true})
        (when (not (s/blank? mmd))
          (try
            (dom/div
              ;; doesn't work
              (dom/div (dom/props {:class "mermaid"})
                       (println "rendering" mmd)
                       (dom/text mmd))
              ;;works
              (dom/div (dom/props {:class "mermaid"})
                       (println "rendering" mmd)
                       (dom/text "flowchart LR\n\nA-->B")))
            (finally (.run js/mermaid (clj->js {:querySelector ".mermaid"})))))))))

Reut Sharabani16:07:11

the try-finally is just an attempt it could not make sense at all 🙂

Dustin Getz16:07:36

yeah i dont think the try/finally is helping you here

Reut Sharabani16:07:52

just wanted to write the run somewhere

Reut Sharabani16:07:42

I think creating a dependency worked, checking on the "real" code now

Reut Sharabani16:07:06

This worked:

(defn mermaid! [_mmd]
  (.run js/mermaid (clj->js {:querySelector ".mermaid"})))
And then:
(when (not (s/blank? mmd))
  (try
    (dom/div (dom/props {:class "mermaid"})
             (println "rendering" mmd)
             (dom/text mmd))
    (finally (mermaid! mmd)))))))))

Dustin Getz16:07:17

why try/finally

Reut Sharabani16:07:49

where would you invoke mermaid! ?

Dustin Getz16:07:56

invoke it like println

Reut Sharabani16:07:31

would it work coincidentally because it's a promise?

Dustin Getz16:07:42

i dont understand

Reut Sharabani16:07:09

doesn't println happens before dom/text is generated?

Dustin Getz16:07:29

(dom/div 
 (do
   (dom/props {:class "mermaid"})
   (dom/text mmd)
   (js/mermaid.run #js {:querySelector ".mermaid"})))

Dustin Getz16:07:37

i have added the explicit do to clarify what is happening

Reut Sharabani16:07:42

it throws some warning like that

Reut Sharabani16:07:51

let me re-check

Reut Sharabani16:07:30

Unserializable reference transfer: #object[Promise] [object Promise]

Reut Sharabani16:07:41

it's not really generating anything dom-able

Reut Sharabani16:07:43

so I don't like it

Dustin Getz16:07:57

i dont understand what you don't like?

Dustin Getz16:07:03

what don't you like and why

Reut Sharabani16:07:15

It gives me this warning on the repl:

Unserializable reference transfer: #object[Promise] [object Promise]

Dustin Getz16:07:29

the warning can be ignored, i will explain that in a moment, it is harmless, i will also explain how to fix the warning

Reut Sharabani16:07:07

ok, but I don't want to take too much of your time

Dustin Getz16:07:36

i want to know "what don't you like and why" Its just the warning that concerned you?

Reut Sharabani16:07:40

I can mess with it some more and learnt he conventions later

Reut Sharabani16:07:08

I think the warning is based on it not being embeddable in the dom (returns promise)

Reut Sharabani16:07:11

but that's just intuition

Dustin Getz16:07:16

that's not it

Dustin Getz16:07:20

the warning is because the promise is returning up the "stack" into an e/server block

Reut Sharabani16:07:37

I have no idea what that means

Reut Sharabani16:07:05

if there is a link feel free to direct me there and let me learn the hard way 🙂

Reut Sharabani16:07:33

but I'm very focused on making things work so I can show it at work tomorrow

Dustin Getz16:07:03

(e/server (e/client (js/Object.))) will warn because the reference cannot be serialized with transit to move over network to the server

Dustin Getz16:07:33

only values can move over network, promise is a ref type and cannot be serializable

👁️ 2
Dustin Getz16:07:15

(e/server
  (let [x (e/client
            (dom/div
              (dom/props {:class "mermaid"})
              (dom/text mmd)
              (js/mermaid.run #js {:querySelector ".mermaid"})))]
    x))
x is the promise and it will fail to transfer

Reut Sharabani16:07:38

oh now I understand what you said about the stack

Dustin Getz16:07:01

the dom elements wrap their children forms in an implicit do (for effect) and return the final element

👍 2
Reut Sharabani16:07:03

I have another meeting now Dustin, but I will continue my exploration later

👍 2
Reut Sharabani16:07:10

thanks for your help!

🙂 2
Reut Sharabani22:07:22

The communication (through datascript) is just incredible! I can't imagine building an api this specific so easily in something like re-frame and rest. Can't wait to test this as an actual service to see how performant it actually is. I built a small ui to see the data flow through the services in the company that employs me. It's not done but I'm getting the hang of it.

🙂 2
Dustin Getz22:07:04

please report back your findings!

👍 2
Reut Sharabani15:07:58

For future reference to those who search in slack: Seems like mermaid dynamically updates the dom in a way electric doesn't like. It appends an svg to the dom (under the mermaid class) and then (for me) it stopped responding (changing mmd doesn't change the dom anymore). I ended up doing it manually like this:

(e/defn Mermaid []
  (e/client
    (try
      (dom/div (dom/props {:class "mmd"})
               (dom/div (dom/props {:id "mermaid"})
                        (dom/text svg)
                        (.then
                          (.render js/mermaid "dummy" mmd)
                          (fn [svg] (let [e (.getElementById js/document "mermaid")]
                                      (println "element" e)
                                      (println "svg" (.-svg svg))
                                      (set! (.-innerHTML e) (.-svg svg)))))
                        nil))
      (finally
        (mermaid! mmd)))))
There are probably better solutions but I don't want to figure it out right now.

grounded_sage16:07:03

Is there a way to use raw html. Say there is an avg I want to use as pure code instead of pulled in as an image.

grounded_sage16:07:25

I can also convert all to the tags to ‘(dom/…)’ I was just wondering if there was a dirtier faster way

Dustin Getz16:07:10

set innerHTML on a string

❤️ 2
Dustin Getz16:07:27

or use a svg file, surely there is a way

Vincent20:07:28

i'm a little sad i missed the zoom meeting, i forgot y'all live in London Lol