Block until a DOM event occurs with core.async - clojurescript

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.

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.

Can i use (if) as a form-2 reagent render-function

I use an (if) condition as form-2 render-function in this way:
(defn bro [dex]
(let [yo (inc dex)]
(if true
[:div (str yo)])))
instead of this way:
(defn bro [dex]
(let [yo (inc dex)]
(fn [dex]
(if true
[:div (str yo)]))))
Is a problem if i use an (if) statement instead of an (fn) function?
And what happens when the statement gone false? The render function returns with nil?
No, this won't work as you expect. To understand the difference, consider the following two components:
(defn test-component-if []
(let [a (atom 1)
_ (.log js/console "let in -if")]
(if (odd? #a)
[:div
[:p "odd"]
[:button {:on-click #(swap! a inc)}
"inc"]]
[:div
[:p "even"]
[:button {:on-click #(swap! a inc)}
"inc"]])))
(defn test-component-fn []
(let [a (atom 1)
_ (.log js/console "let in -fn")]
;; I dub thee test-inner
(fn []
(if (odd? #a)
[:div
[:p "odd"]
[:button {:on-click #(swap! a inc)}
"inc"]]
[:div
[:p "even"]
[:button {:on-click #(swap! a inc)}
"inc"]]))))
test-component-fn works as expected, while test-component-if does not. Why is that? Well when a reagent component can return one of two things (I'm ignoring "type-3" components, as that hooks into react knowledge). It can return
a vector
another function
If it returns a vector, the function itself becomes the render function, in our case test-component-if. When it returns a function, the function that was returned, not the original function, is the render function. In this case, what I have dubbed test-inner
When Reagent calls a render function, it tracks the atoms that function accesses, and whenever that atom changes it calls the render function. So what happens when we use test-component-if?
Reagent calls test-component-if
Our let clause binds a new atom a to 1.
A vector is returned
We click the button
The atom a is incremented
Reagent sees the change to a and calls test-component-if
Our let clause binds a new atom a to 1. (A different atom than our previous a)
Ooops!
So a is always 1. You can verify this by looking at the console, and seeing that the message is printed every time you click the button.
Now how about test-component-fn?
Reagent calls test-component-fn
Our let clause binds a new atom a to 1.
test-component-fn returns test-inner which closes over a
Reagent calls test-inner
We click the button
a is incremented
Reagent sees the change to a and calls test-inner
Repeat as many times as you want.
You can verify that let only gets executed once again on the console. Click the button as many times as you want, the message will only be printed when it's first rendered.
In terms of an if without an else clause, this will indeed return nil. It's convention to use when instead of if in such cases, which makes it clear the lack of an else is intended. It also has the advantage of including an implicit do. As for what Reagent will do when it encounters that nil, in most cases it will silently remove it and display nothing.
Is a problem if i use an (if) statement instead of an (fn) function?
I think you meant to use an if inside an fn function, but either way it's not a problem.
And what happens when the statement gone false? The render function returns with nil?
Reagent handles these gracefully, a nil will be skipped (no corresponding child is created). If you see the TODOs app example in the official docs, you'll see source has code like the following:
(when (pos? done)
[:button#clear-completed {:on-click clear-done}
"Clear completed " done])]))
In this case, if done is not a positive number, the return value of this expression is nil and the button to clear the completed tasks is simply not added to the DOM.

Calling a function from another namespace in ClojureScript

I'm a newbie with CojureScript because I got the LISP itch some months ago and then I migrated the API to Clojure and I'm loving it. Now I want to migrate the frontend too but this is my first week with CLJS.
So, I have this function:
(defn foo []
(events/listen (gdom/getElement "icon-add") EventType.CLICK
(fn [e] (.log js/console (str ">>> VALUE >>>>> " e)))))
the function works great in the core.cljs file, but if I move it to users.cljs file and I call it from the core namespace with:
(ns zentaur.core
(:require [zentaur.users :as users]
(users/foo)
the DOM element "icon-add" is never found and instead I get the error message:
Uncaught goog.asserts.AssertionErrorĀ {message: "Assertion failed: Listener can not be null.", reportErrorToServer: true,
in the browser console. If I move the function back to core.cljs, all works fine again. Then my question is: how can I move a function to another NS and be sure it keeps working?
UPDATE:
I noted that if I call the listener directly in users.cljs:
(events/listen (gdom/getElement "icon-add") EventType.CLICK
(fn [] (.log js/console (str ">>> events/listen in users ns"))))
(I mean out of any function), all works fine, the code find the DOM element.
SECOND UPDATE
Thanks a lot for your answers but this issue looks like a "compiling time" problem. All the listeners:
(events/listen (gdom/getElement "icon-add") EventType.CLICK foo-funct)
must be loaded when CLJS runs at first time. Loading another ns is a "second time" thing and then the DOM is not reachable anymore.
In the core namespace you need to require the 2nd namespace:
(ns xyz.core
(:require [xyz.users :as users] ))
(users/foo) ; call the function
This assumes your code is laid out as
src
src/xyz
src/xyz/core.cljs
src/xyz/users.cljs
There are also some good ideas here on various tradeoffs in naming and references to other namespaces.
The user namespace is special in that it is designed to be pre-loaded, and for development only. It is also the only time you will ever see a namespace that does not have a parent package. I say this just to warn you off using user as a namespace name - doing so will just cause confusion - whether it has a parent package or not.
Your problem seems to be that one of the arguments to events/listen is somehow returning nil. Do you have gdom as a require in zentaur.users, so: [goog.dom :as gdom]?
Google Closure library throws the assertion error because your listener function, i.e., the third parameter to listen is null.
Looking at the source code for the library, it tries to wrap the third parameter into a Listener, and the error string is produced from this failed assertion for a truthy value.
I often have similar problem, when I accidentally put extra parenthesis around the function. In effect, this leads to the function being called immediately after its declaration. This may happen without warning, even when your declared function requires one or more parameters; ClojureScript runs on JS, which does not check the arity when calling the function. Since the body of your listener is just console.log, which returns null, you would be trying to assign the null as listener.
You did note that the code example that you give is working in core.cljs. Indeed, the example does not show any obvious reason why the function is null in this case. Perhaps there was a small error in copy-pasting this function to users.cljs, e.g., extra parenthesis added?

How to use an atom which has the result of a http-request in reagent

This is what I'm using to make a remote call using the clj-http library.
(defn make-remote-call [endpoint]
(go (let [response (<! (http/get endpoint
{:with-credentials? false}))])))
(reset! app-state response)
;(js/console.log (print response)))))
The above print to console works fine
(defn call []
(let [x (r/atom (make-remote-call site))]
(js/console.log x)
this spits out #object[cljs.core.async.impl.channels.ManyToManyChannel] in the console.
What can I do to return the response in the make-remote-call function.
I used the response to set an atom's value. Trying to reference values inside the atom results in errors like "Uncaught Error: [object Object] is not ISeqable" and No protocol method IDeref.-deref defined for type null:
Any idea what I might be doing wrong?
Please let me know if I need to provide any additional info
make-remote-call is returning a channel. Try interrogating this channel to see what's inside it.
This question should help:
Why do core.async go blocks return a channel?
I think you know this already, but you need to de-reference an atom i.e. get what is inside an atom, using #. Infomally speaking, the value you want is wrapped in two containers, so you need to get what's inside the atom, then what's inside the channel.

Clojurescript - Uncaught Error: <! used not in (go ...) block

I am in Clojurescript, and trying to use core.async to get a result from a native Javascript function (I am in the browser), and conditionally integrate it into a map.
I have a function to wrap a native browser call (inspired by Timothy Baldridge's talk on core.async, approx. 34 min for those interested) :
(defn geolocation []
(let [c (chan)
cb (fn [e] (put! c (js->clj e)))]
(if (exists? js/navigator.geolocation)
(.getCurrentPosition js/navigator.geolocation. cb cb)
(put! c {:error "Geolocation is not supported"}))
c))
I can use it like that :
;; this works
(go (.log js/console "RES1 -" (<! (geolocation))))
;; this too (not sure how to print it though)
(go (println "RES2 - " (-> {}
(assoc :test (<! (geolocation))))))
;; this fails... why ?
(go (println "RES3 - " (-> {}
(#(when true
(assoc % :test (<! (geolocation))))))))
The last example fails with an error : Uncaught Error: <! used not in (go ...) block, even though I thought I was using the same kind of structure.
What am I doing wrong ?
Note : I am requiring it properly from the :require-macro directive, like described here.
The go macro will take the body it's given and transform all <! and alt! and >! calls to a state machine. However it won't walk into functions:
couldn't use for loop in go block of core.async?
By stops translation at function boundaries, I mean this: the go block takes its body and translates it into a state-machine. Each call to <! >! or alts! (and a few others) are considered state machine transitions where the execution of the block can pause. At each of those points the machine is turned into a callback and attached to the channel. When this macro reaches a fn form it stops translating. So you can only make calls to <! from inside a go block, not inside a function inside a code block.
This is part of the magic of core.async. Without the go macro, core.async code would look a lot like callback-hell in other langauges.