This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-05-15
Channels
- # babashka (12)
- # beginners (88)
- # calva (6)
- # cider (4)
- # clerk (110)
- # clojure (18)
- # clojure-czech (1)
- # clojure-europe (26)
- # clojure-nl (1)
- # clojure-norway (7)
- # clojure-poland (8)
- # clojure-spain (2)
- # clojure-uk (2)
- # clojurescript (22)
- # cursive (11)
- # data-science (1)
- # datalevin (5)
- # datomic (35)
- # events (1)
- # fulcro (2)
- # gratitude (5)
- # helix (4)
- # hoplon (20)
- # hyperfiddle (52)
- # jobs (3)
- # lsp (1)
- # malli (48)
- # missionary (11)
- # off-topic (31)
- # practicalli (1)
- # reitit (7)
- # releases (1)
- # remote-jobs (7)
- # scittle (9)
- # shadow-cljs (7)
- # sql (11)
- # xtdb (5)
I’d like to put a text area into a clerk notebook which dynamically evaluates the contents of the text area with sci and outputs the result to a pre
tag.
I’m sure I’d seen something that did this somewhere, but can’t seem to find it…
any pointers?
Can you give an example of what you’d like to output?
where are the rendered notebooks/*
from the clerk repo available? the cherry notebook has one such example, probably it's possible to use SCI as the evaluator too
I’d like an editable textarea with a sci form in it, e.g.
(range)
And I’d like that textarea to be evaluated on every change or on a submit button with preferably either the clerk data structure viewers displaying the result, or a pr-str
in a pre
tag.
So in the above case the result would obviously be something like:
(1 2 3 4 5 6 7 8 9 10 ...)
@U06HHF230 See this notebook: https://snapshots.nextjournal.com/clerk/build/5e875e256a28a6deabf27bd6fc20f44cee5dad20/index.html#/notebooks/cherry.clj
ahh awesome! Thanks 🙇
If you would rather use SCI for evaluation I think you could just use load-string
on the contents of your text-area
hmm I get error in render-fn: Could not resolve symbol: nextjournal.clerk.cherry-env/cherry-compile-string
in the browser env
ahh maybe missing a def
hmm no doesn’t look like it
This is only available from clerk from the main branch I think... right @U5H74UNSF?
but as I said, you can also just use load-string
on the code string in your text area via SCI
> but as I said, you can also just use load-string on the code string in your text area via SCI I’m trying to understand what this means exactly… I don’t really understand the clerk evaluation model; and how much magic there is
• the example from the cherry notebook should work if you are using the main version with git/sha • this should work if you are using the clojars version:
(clerk/with-viewer
{:evaluator :sci
:render-fn
'(fn [value]
(let [default-value "(defn foo [x] (+ x 10))
(foo 10)"
!input (reagent.core/atom default-value)
!val (load-string @!input)
click-handler (fn []
(reset !val (try (load-string @!input))))]
(fn [value]
[:div
[:div.flex
[:div.viewer-code.flex-auto.w-80.mb-2 [nextjournal.clerk.render.code/editor !input]]
[:button.flex-none.bg-slate-100.mb-2.pl-2.pr-2
{:on-click click-handler}
"Compile!"]]
[nextjournal.clerk.render/inspect
@!val]])))}
nil)
Note that the above code might need some tweaks, I just edited by hand and probably made a mistake or two
@U06HHF230 what’s your use case for this feature, btw?
The use case isn’t anything major… Essentially I’m doing an intro to clojure talk, and I’m using clerk-slideshow, and would like my simple code evaluation examples to be editable in my slides, so I don’t need to faff about switching to my text editor etc
so I can live-code some examples in my slides if there are questions etc
@U04V15CAJ: hmm your modified example gives me the same error :thinking_face:
you could also use #C1VHLE27L
Yeah using klipse had occurred to me too… but I was assuming that as clerk has most of the pieces it’d be a little less faffy to use what it provides, and would enable more consistent integration with the data viewers etc
ahh maybe its a caching problem
ok those problems sorted… but still not quite working :thinking_face:
that one’s sorted now
now I’m not getting an error, the first eval works, but clicking compile after making changes isn’t re-evaluating the form… currently debugging
hmm, it seems the atom isn't updated properly. This is what I have now:
(clerk/with-viewer
{:evaluator :sci
:render-fn
'(fn [value]
(let [default-value "(defn foo [x] (+ x 10))
(foo 10)"
!input (reagent.core/atom default-value)
!val (atom (load-string @!input))
click-handler (fn []
(prn @!input)
(reset! !val (load-string @!input)))]
(fn [value]
[:div
[:div.flex
[:div.viewer-code.flex-auto.w-80.mb-2 [nextjournal.clerk.render.code/editor !input]]
[:button.flex-none.bg-slate-100.mb-2.pl-2.pr-2
{:on-click click-handler}
"Eval!"]]
[nextjournal.clerk.render/inspect
@!val]])))}
nil)
Ah gotcha, I needed to change atom
to reagent.core/atom
:
(clerk/with-viewer
{:evaluator :sci
:render-fn
'(fn [value]
(let [default-value "(defn foo [x] (+ x 10))
(foo 10)"
!input (reagent.core/atom default-value)
!val (reagent.core/atom (load-string @!input))
click-handler (fn []
(prn @!input)
(reset! !val (load-string @!input)))]
(fn [value]
[:div
[:div.flex
[:div.viewer-code.flex-auto.w-80.mb-2 [nextjournal.clerk.render.code/editor !input]]
[:button.flex-none.bg-slate-100.mb-2.pl-2.pr-2
{:on-click click-handler}
"Eval!"]]
[nextjournal.clerk.render/inspect
@!val]])))}
nil)
And this is with better error handling:
(clerk/with-viewer
{:evaluator :sci
:render-fn
'(fn [value]
(let [default-value "(defn foo [x] (+ x 10))
(foo 10)"
!input (reagent.core/atom default-value)
safe-load-string (fn [s]
(try (load-string s)
(catch :default e e)))
!val (reagent.core/atom (safe-load-string @!input))
click-handler (fn []
(reset! !val (safe-load-string @!input)))]
(fn [value]
[:div
[:div.flex
[:div.viewer-code.flex-auto.w-80.mb-2 [nextjournal.clerk.render.code/editor !input]]
[:button.flex-none.bg-slate-100.mb-2.pl-2.pr-2
{:on-click click-handler}
"Eval!"]]
[nextjournal.clerk.render/inspect
@!val]])))}
nil)
hmm I just made that same change, but doesn’t seem to work… trying yours incase there’s something else up
ah ok yours works
@U06HHF230 theres examples of this in https://github.com/nextjournal/clerk/blob/5e875e256a28a6deabf27bd6fc20f44cee5dad20/notebooks/viewers/code.clj
hmm so changing borkdude’s example to drop the eval button and use an :on-change
handler on the editor doesn’t appear to work :thinking_face:
(clerk/with-viewer
{:evaluator :sci
:render-fn
'(fn [value]
(let [default-value "(defn foo [x] (+ x 10))
(foo 30)"
!input (reagent.core/atom default-value)
!val (reagent.core/atom (load-string @!input))
click-handler (fn []
(prn @!input (load-string @!input))
(reset! !val (load-string @!input)))]
(fn [value]
[:div
[:div.flex
[:div.viewer-code.flex-auto.w-80.mb-2 [nextjournal.clerk.render.code/editor
!input
{:on-change click-handler}]]
]
[nextjournal.clerk.render/inspect
@!val]])))}
nil)
this is simpler
(def editor-sync-viewer
(assoc viewer/viewer-eval-viewer :render-fn '(fn [!code _]
[:div.bg-neutral-50 [nextjournal.clerk.render.code/editor !code]])))
then use it via
^{::clerk/sync true ::clerk/viewer editor-sync-viewer}
(defonce editable-code (atom "(def fib
(lazy-cat [0 1]
(map + fib (rest fib))))"))
I believe @U9EQP1K0X you have plenty of examples of eval with an editable code viewer, right?
@U5H74UNSF: where do the evaluation results in that example get displayed?
Or are you suggesting to combine it with the borkdude snippet?
@U06HHF230 do you want to eval on change or on click?
eval on change preferably
if it gets too slow I can debounce it
but the snippets I’m planning to use it with should be fine for on-change
SCI would be fine… but if it’s possible to get it to do in the JVM without too much hoop jumping that would also be ace
I might do some interop examples… but I was mainly just hoping to do basic evaluation with these examples, so sci would be fine
so the two snippets above and a cell with (eval (read-string @editable-code))
should work (sorry just typing on 📱 )
a cell?
ok I think I see what the last example is actually doing now :thinking_face:
what context does :render-fn
in those snippets run in?
I’m assuming they’re just SCI or CLJ but SCI/CLJS in the browser?
render fns run in the browser through sci, sync atom state is synced to the JVM (and back)
Does sci do meta-circular eval? i.e. from within the sci render function can you run sci’s eval-string?
yeah ok — playing with mkvlr’s one
that seems to work
;; # :necktie: Code Viewer
(ns viewers.code
{:nextjournal.clerk/no-cache true}
(:require [nextjournal.clerk :as clerk]
[nextjournal.clerk.viewer :as viewer]
))
(def editor-sync-viewer
(assoc viewer/viewer-eval-viewer :render-fn '(fn [!code _]
[:<>
[:div.bg-neutral-50 [nextjournal.clerk.render.code/editor !code]]
[:pre (load-string @!code)]])))
^{::clerk/sync true ::clerk/viewer editor-sync-viewer
::clerk/visibility {:code :hide}}
(def editable-code
(atom "(defn add [& args] (apply + args))
(add 10 20)"))
;; # :necktie: Code Viewer
(ns viewers.code
{:nextjournal.clerk/no-cache true}
(:require [nextjournal.clerk :as clerk]
[nextjournal.clerk.viewer :as viewer]
))
(def editor-sync-viewer
(assoc viewer/viewer-eval-viewer :render-fn '(fn [!code _]
[:div.bg-neutral-50 [nextjournal.clerk.render.code/editor !code]])))
^{::clerk/sync true ::clerk/viewer editor-sync-viewer
::clerk/visibility {:code :hide}}
(def editable-code
(atom "(defn add [& args] (apply + args))
(add 10 20)"))
(load-string @editable-code)
I think I tried a variant like that but it didn’t seem to refresh the evaluated result 👀
I guess to fix that you need to put the result in an atom too??
that sync thing seems to work, but it seems it re-evaluates all the cells of the entire nodebook on every character of input
this works, it was missing a defonce
and I removed the no-cache:
(ns scratch
(:require [nextjournal.clerk :as clerk]
[nextjournal.clerk.viewer :as viewer]))
(def editor-sync-viewer
(assoc viewer/viewer-eval-viewer :render-fn '(fn [!code _]
[:div.bg-neutral-50 [nextjournal.clerk.render.code/editor !code]])))
^{::clerk/sync true
::clerk/viewer editor-sync-viewer
::clerk/visibility {:code :hide}}
(defonce editable-code
(atom "(defn add [& args] (apply + args))
(add 10 20)"))
(load-string @editable-code)
or if you want o eval in sci:
(ns scratch
(:require [nextjournal.clerk :as clerk]
[nextjournal.clerk.viewer :as viewer]))
(def editor-sync-viewer
(assoc viewer/viewer-eval-viewer :render-fn '(fn [!code _]
[:div.bg-neutral-50 [nextjournal.clerk.render.code/editor !code]])))
^{::clerk/sync true
::clerk/viewer editor-sync-viewer
::clerk/visibility {:code :hide}}
(defonce editable-code
(atom "(defn add [& args] (apply + args))
(add 10 20)"))
(clerk/eval-cljs-str @editable-code)
yeah ok it does work but needs some error handling not to also nuke your whole editing environment on a syntax error
here’s eval on click:
(ns scratch
{::clerk/visibility {:code :hide}}
(:require [nextjournal.clerk :as clerk]
[nextjournal.clerk.viewer :as viewer]))
(def editor-sync-viewer
(assoc viewer/viewer-eval-viewer :render-fn '(fn [!code _]
[:div.bg-neutral-50 [nextjournal.clerk.render.code/editor !code]])))
^{::clerk/sync true
::clerk/viewer editor-sync-viewer}
(defonce editable-code
(atom "(defn add [& args] (apply + args))
(add 10 20)"))
^{::clerk/sync true}
(defonce result (atom (load-string @editable-code)))
(defn eval! []
(reset! result (load-string @editable-code)))
(clerk/with-viewer {:render-fn '(fn [_]
[:button.bg-blue-500.p-2.rounded {:on-click #(nextjournal.clerk.render/clerk-eval '(eval!))}
"Eval!"])}
{})
#_(eval!)
@U04V15CAJ right only thing that didn’t work about your example for me is that I didn’t see the eval button
It was there — it did render a little strange at the right, so might have been easily missed
so you want the example from @U04V15CAJ but with on-change instead of on-click?
thing there is if you overwrite the on-change handler – you’re in control of swapping the atom state
try
(clerk/with-viewer
{:render-fn
'(fn [value]
(let [default-value "(defn foo [x] (+ x 10))
(foo 30)"
!input (reagent.core/atom default-value)
!val (reagent.core/atom (load-string @!input))
on-change (fn [code]
(reset! !input code)
(prn @!input (load-string @!input))
(reset! !val (load-string @!input)))]
(fn [value]
[:div
[:div.flex
[:div.viewer-code.flex-auto.w-80.mb-2 [nextjournal.clerk.render.code/editor
!input
{:on-change on-change}]]
]
[nextjournal.clerk.render/inspect
@!val]])))}
nil)
yeah I think this is the sort of UX I like best… Except perhaps if there’s an error to note also nuke the editor but I think that can be done in the renderer
Is it possible to put an error boundary in between the sub components
This seems to work how I’d like:
(ns scratch
{:nextjournal.clerk/no-cache true}
(:require [nextjournal.clerk :as clerk]
[nextjournal.clerk.viewer :as viewer]
))
;; # latest
(clerk/with-viewer
{:render-fn
'(fn [value]
(let [default-value "(defn add [& args] (apply + args))
(add 30)"
!input (reagent.core/atom default-value)
!val (reagent.core/atom (load-string @!input))
on-change (fn [code]
(reset! !input code)
;;(prn @!input (load-string @!input))
(reset! !val (try (load-string @!input)
(catch Error ex
ex))))]
(fn [value]
[:div
[:div.flex
[:div.viewer-code.flex-auto.w-80.mb-2 [nextjournal.clerk.render.code/editor
!input
{:on-change on-change}]]]
[nextjournal.clerk.render/inspect
@!val]])))}
nil)
I know it doesn’t work on the JVM but I can live with that
weird, in the emacs browser I see this:
when I use the backspace key. works fine in safari and brave
I’m currently struggling to debug an issue with the error handling in the code snippet below:
(ns scratch
{:nextjournal.clerk/no-cache true}
(:require [nextjournal.clerk :as clerk]
[nextjournal.clerk.viewer :as viewer]
))
;; # latest
(clerk/with-viewer
{:render-fn
'(fn [value]
(let [default-value "(map [] str)"
!input (reagent.core/atom default-value)
!val (reagent.core/atom (load-string @!input))
on-change (fn [code]
(reset! !input code)
;;(prn @!input (load-string @!input))
(reset! !val (try (load-string @!input)
(catch Error ex
ex))))]
(fn [value]
[:div
[:div.flex
[:div.viewer-code.flex-auto.w-80.mb-2 [nextjournal.clerk.render.code/editor
!input
{:on-change on-change}]]]
[nextjournal.clerk.render/inspect
@!val]])))}
nil)
Note the intentional erroneous arg order (map [] str)
…
I don’t understand why this error appears to escape the catch
, and nuke the whole component, but syntax errors are caught.
Any ideas?I suspect it’s to do with the root exception type in sci!?! Not sure how that works in sci, is it inherited from the host, so here presumably cljs? Also it seems that the initialisation is run twice :thinking_face:
> the initial load-string of the default-value is not wrapped in a try/catch maybe? oh sorry I have a newer version that fixes that… but it still happens > it escapes the catch because of laziness? :thinking_face:
$ clj
Clojure 1.10.3
user=> (try (map [] str) (catch Exception e 1))
Error printing return value (IllegalArgumentException) at clojure.lang.RT/seqFrom (RT.java:557).
Don't know how to create ISeq from: clojure.core$str
(user=>
👍 nice one
I should have thought of that!
This variation seems to fix the issue nicely:
(def editor-sync-viewer
{:evaluator :sci
:render-fn
'(fn [value]
(let [safe-load-string (fn [s]
(try
(let [ret (load-string s)]
(if (seq? ret)
(try
(first ret)
ret
(catch :default e
e))))
(catch :default e
e)))
!input (reagent.core/atom value)
!val (reagent.core/atom (safe-load-string @!input))
on-change (fn [code]
(reset! !input code)
(reset! !val (safe-load-string @!input)))]
(fn [value]
[:div
"stuff"
[:div.flex
[:div.viewer-code.flex-auto.w-80.mb-2 [nextjournal.clerk.render.code/editor
!input
{:on-change on-change}]]]
[:em "=> "]
[nextjournal.clerk.render/inspect
@!val]])))})
(clerk/with-viewer editor-sync-viewer
"(map inc [1 2 3 4])")