Can i store my app's content (reagent components) in the re-frame db? - clojurescript

Here is my app's structure:
(reg-event-db
:app-surface-add-layer!
(fn [db [_ layer-content & [layer-params]]]
(assoc db :app-surface-layers
(conj (:app-surface-layers db)
{:layer-content layer-content
:layer-params layer-params}))))
(reg-sub
:app-surface-get-layers
(fn [db _]
(:app-surface-layers db)))
(defn x-app []
(let [app-surface-layers (subscribe [:app-surface-get-layers])]
(fn []
[:div#app
[:div#app-topbar "Menu-1, Menu-2, etc."]
[:div#app-sidebar "Menu-3, Menu-4, etc."]
[:div#app-surface
(reduce
(fn [app-surface layer-data]
(conj app-surface
[(:layer-content layer-data) (:layer-params layer-data)]))
[:div#app-surface-layers]
#app-surface-layers)]])))
(defn calendar [params]
[:div#calendar
"Calendar"])
; Put the calendar component to the re-frame db
(dispatch [:app-surface-add-layer! #'calendar ["Param-1"]])
My question is, can i store my app's content component (like calendar in the example) in the re-frame db?
What do you think about this solution's performace?

Can you? Absolutely, it's all just vectors in the end, and it certainly won't interfere with performance. Readability is a bit rough, I think it'd be clearer to have a case or similar to make it easier to tell in which situations calendar will be rendered vs a different component when trying to read the code, but a lot of that is going to depend on the specifics of what your doing.

Related

Using an anonymous Js function in a go block

I'm attempting to do some timed animation in clojurescript/reagent and I'm trying to use core.async to achieve series of timed steps in order. I'm using a third party js react library, so, to call its functions I'm using the form (fn [] ^js (.function (.something #ref)). However putting this anonymous function inside the go block as follows doesn't work -
(go
(js/console.log "going")
(<! (timeout 3000))
(fn [] ^js (.function (.somedata #lib))
(js/console.log "landed")
)
This returns "going" and "landed" timed correctly and works when putting another console.log function in its place. However if you wrap this console.log in an Fn it no longer gets called. It is being called in the :on-click handler of a component. What have I missed?
That ^js there is unnecessary.
When you wrap something in (fn [] ...), that something is not called unless that fn is called. I.e. ((fn [] ...)). But then, you end up creating a function and calling it immediately, which is completely unnecessary.
So, assuming I understand you correctly, you can simply replace that (fn [] ^js ...) with (-> #lib .somedata .function).
On a side note, somedata sounds like it's just a field that has some data and not a function that needs to be called. If that's the case, use .-somedata instead of .somedata.
As mentioned by Eugene Pakhomov using (fn [] ...) only creates a function, it does not call it. Thus it it basically just elimnated entirely without doing anything.
Your motiviation here seems to get rid of the inference warning. So the underlying problem is that core.async is rather forgetful when in comes to type hints. If you ask me you shouldn't do any interop in go blocks at all and rather move it all out. Either via defn or local function outside the go.
(defn do-something []
(.function (.somedata ^js #lib)))
(go
(js/console.log "going")
(<! (timeout 3000))
(do-something)
(js/console.log "landed"))
(let [do-something (fn [] (.function (.somedata ^js #lib)))]
(go
(js/console.log "going")
(<! (timeout 3000))
(do-something)
(js/console.log "landed")))
Also, just a word of caution. Using core.async for this will substantially increase the amount of code generated for code in go blocks. If you really need to do is delay something use (js/setTimeout do-something 3000). Use go with caution, it'll easily generate 10x the code for some things than would normally be required.

CLJS: Setting a JS property to the result of calling a method on it

I'm looking to modify some existing text in a web app, which I'm accessing with expressions like this:
(.-innerHTML (.getElementById js/document "myElementId"))
What I specifically want to do is mutate that text via a search/replace:
(set! (^that whole thing) (.replace (^that whole thing) "old" "new"))
And I'd ideally like to do that without having to repeat the property-access expression. Is there any existing shortcut for this? I'm envisioning something like Raku's object .= method(args) shorthand for object = object.method(args); maybe a good clj name/pattern would be (.set! (access-expression) method & args). I can always write a macro, but was wondering if I'd overlooked something already there.
I was curious too and looked in the CLJS CheatSheet and they link to the CLJS Oops library which provides similarly looking functions.
It seems to be that the most robust solution would be to just rely on the Google Closure Library (which is always at hand) and use a combination of goog.object/get and goog.object/set (no need for a macro), something like:
(require 'goog.object)
(defn update-obj! [obj field f & args]
(let [old-val (goog.object/get obj field)
new-val (apply f old-val args)]
(goog.object/set obj field new-val)))
;; Example:
(update-obj! (.getElementById js/document "my-div") "innerHTML" (partial str "it works: "))
This should work in both development and optimized output.

Subscribe and add-watch in uberjar

I have component created with reagent/create-class which gets atom created by subscribe. I am adding a watch on :component-did-mount in order to call component (js) function on request, which is triggered by change in the atom (there is a server round trip). It looks somewhat as following:
(defn editor [text issue-hints]
(let []
(reagent-core/create-class
{:component-did-mount
#(let [editor (js/SimpleMDE.
(clj->js {...}))]
(do
...
(add-watch issue-hints :watch-issue-hints (show-hint (-> editor .-codemirror)))
...))
:reagent-render
(fn [this] [:textarea])})))
(defn edit-panel [text]
(let [test (re-frame.core/subscribe [:issue-hints])]
[box
:class "issue-detail"
:size "auto"
:child [:div.issue-detail [editor text test]]]))
It works well when debugging the project, but once uberjar file is run, watch handler never gets called. What is the most strange thing to me is that if at least dummy reference to subscription atom is added, it works well again (eg. dummy #issue-hints in same let as subscription). Server round trip looks good.
Can someone give me explanation and/or suggestion for more reasonable fix/workaround?
It looks like you need two params in :reagent-render, not one -
:reagent-render
(fn [text issue-hints] [:textarea])}))) ---> Not "this", but should match initial args
When you only pass one arg, and it is not derefed in the component-did-mount fn, it won't receive the subscription on subsequent changes.
Further, I don't think that you need to explicitly use add-watch, as that is what the re-frame subscription is giving you ootb. By using the deref syntax #issue-hints, the element will be notified any time a change happens to issue-hints, and you should be able to watch the state from the app-db.
If you add the 2nd arg, my guess is that you could drop the add-watch and it should work as expected.
Here are the docs and if you look at the code sample, you see the repetition of the args...
----- Edit: Will Form-2 work? -----
(defn editor [text issue-hints]
(let [hints #issue-hints
editor (js/SimpleMDE. (clj->js {...})] ;;-> This will not be dynamic, so consider moving to returned fn if necessary
(fn [text issue-hints]
(if hints
[:textarea (special-hint-handler hints)]
[:textarea]
))))
So based on the comments, this would give you a watcher on issue-hints, and you could respond accordingly. The subscription does not necessarily need to be used on the DOM.

Block until a DOM event occurs with core.async

I've got a Clojurescript project where i need to block the whole thread execution until an DOM event occurs.
In this case, the event is DOMContentLoaded, which fire when the initial HTML document has been completely loaded and parsed. But it could be extended to any DOM (or non-DOM) event.
As i'm new to Clojurescript and async i wasn't sure how to solve this problem. My first guess was to use the core.async library. After some doc scraping, i came with that function:
(defn wait-dom-loading
[]
(let [c (async/chan)]
{1} (.addEventListener js/document "DOMContentLoaded" (fn [] (async/go (async/>! c true))))
{2} (async/go (async/<! c))))
The way i understand it is that {2} takes from chan c and is parked until the listener in {1} evaluates the function and puts a value in chan c.
As i barely understand how to do unit tests on asynchronous code (beside puting it in an (async done) expression and calling done when done) i can't verify if what i did is correct. I tried this snippet:
(do
(wait-dom-loading)
(-> (dommy/sel1 :p)
(dommy/set-text! "Loaded !")))
With a p block inside an html page, and noticed that the console complains about the js code trying to manipulate a DOM object that don't yet exists. That confirms that what i did didn't work as planned.
What does seems wrong in this example ?
Is this overkill ? Could i solve that with a smaller solution or even gasp a built-in funtion ?
Is putting my script at the bottom of my html page a not so bad practice ?
As this was my first question on stack overflow, i hope it is well-written enough.
This is how i would do it:
(ns domevent.core
(:require [cljs.core.async :as async :refer [chan]])
(:require-macros [cljs.core.async.macros :refer [go]]))
(enable-console-print!)
(def ch (chan))
(go
(pr "i am waiting")
(pr (<! ch))
(dommy/set-text! (dommy/sel1 :p) "Loaded !"))
(.addEventListener js/document "DOMContentLoaded" (fn [] (go (>! ch "hello"))))
Or more simply:
(let [ch (chan)]
(go
(pr "i am waiting")
(<! ch)
(dommy/set-text! (dommy/sel1 :p) "Loaded !"))
(.addEventListener js/document "DOMContentLoaded" #(close! ch)))
)
The point here is the ch, which is shared by reader and writer. When (<! ch) happens, there is nothing in ch yet, so this thread is parked (i.e. stops and waits for anything to appear in ch). Meanwhile, the DOMContentLoaded occurs, and the handler writes to the channel. Then the former thread continues.

Can name munging be avoided for an interop call in ClojureScript?

In advanced compilation
(js/console.log "HELLO"
js/window.navigator.msSaveBlob
(.. js/window -navigator -msSaveBlob)
(aget js/window "navigator" "msSaveBlob")
js/console.log)
=>
HELLO undefined undefined function function
I think this means that js/console has some provided externs, but navigator does not (or at least not the ms specific stuff).
AFAIK the only way to avoid this is to create some externs? But this seems unnecessarily obtuse; why would you ever want js/anything to be munged?? Wouldn't it make make more sense to never munge js/anything interop?
System functions are not munged; only your own functions are. You probably want (.log js/console ...) ?
For de-munging your own functions, place ^:export between the defn and the function name to export its name intact.
Here is more information.
All see the section called "munging" here.