om - data not updating after ajax call - clojurescript

I have the following code and I think I am doing something fundamentally wrong.
(defn world-view [data owner opts]
(reify
om/IInitState
(init-state [_]
(om/update! data #(assoc % :world vec)))
om/IWillMount
(will-mount [_]
(go (let [world (<! (get-world (:dimensions opts)))]
(log (get-in world [9 9]))
(om/update! data #(assoc % :world world)))))
om/IRender
(render [this]
(log (get :world data))
(apply dom/table nil
(om/build-all row (:world data))))))
I am making a remote ajax call in om/IWillMount like this:
om/IWillMount
(will-mount [_]
(go (let [world (<! (get-world (:dimensions opts)))]
(log (get-in world [9 9]))
(om/update! data #(assoc % :world world)))))
I am expecting a 2d vector back from the remote call and this works because I can log elements of the array with
(log (get-in world [9 9]))
I am then using om/update to update the data structure.
But when render is called, the data does not appear to be updated, I can test this by trying to log the data structure
log (get :world data))
This logs nothing.
I can't see what I am doing wrong or why the data structure has not been updated.

om/update! sets the cursor to the given value, so you're basically assigning a function to your cursor. I believe you should use om/transact! instead.

Related

How do I extract the body from an HTTP request in Clojure?

I am making an HTTP request:
(defn main-panel []
(def API-URL "https://api.chucknorris.io/jokes/random")
(defn getFileTree []
(go (let [response (<! (http/get API-URL
{:with-credentials? false
:headers {"Content-Type" "application/json"}}))]
(:status response)
(js/console.log (:body response))))) ; prints a very complex data structure
(go
(let [result (<! (getFileTree))]
(.log js/console (:body result)))) ; prints null
:reagent-render
(fn []
[:h1 "kjdfkjndfkjn"]))
But I can't get to the "joke" in the returned object, array item 13:
How do I assign this value to a let or def?
Also, why does the second console.log print null?
Update
I am now moving on from using reagent atoms to reframe.
This is my component that successfully GETs data, updates the re-frame 'database':
(defn main-panel []
(def API-URL "https://api.chucknorris.io/jokes/random")
(def request-opts {:with-credentials? false})
(defn getFileTree []
(go (let [response (<! (http/get API-URL request-opts))]
(re-frame/dispatch [:update-quote response]))))
(defn render-quote []
(println (re-frame/subscribe [::subs/quote])) ;successfully prints API data as in screenshot below
(fn []
(let [quote-data (re-frame/subscribe [::subs/quote])
quote-text (if quote-data (:value quote-data) "...loading...")]
[:div
[:h3 "Chuck quote of the day"]
[:em quote-text]])))
(fn []
(getFileTree)
[render-quote]))
But this is the object I get back from the re-frame database:
As you can see it comes wrapped in the Reaction tags and I can't access the body or value any more. How do I access those?
I have a small working version using the reagent template. Create a new project (assuming you have Leiningen installed) with: lein new reagent chuck. This will create a project with many dependencies, but it works out of the box.
Next, edit the file at src/cljs/chuck/core.cljs and edit it so it looks like the following:
(ns chuck.core
(:require-macros [cljs.core.async.macros :refer [go]])
(:require [reagent.core :as reagent :refer [atom]]
[cljs-http.client :as http]
[cljs.core.async :refer [<!]]))
(def api-url "https://api.chucknorris.io/jokes/random")
(def request-opts {:with-credentials? false
:headers {"Content-Type" "application/json"}})
(def api-response (atom nil))
(defn get-quote []
(go
(let [response (<! (http/get api-url request-opts))]
(println response)
(reset! api-response response))))
(defn render-quote []
(fn []
(let [quote-data (:body #api-response)
quote-text (if quote-data (:value quote-data) "...loading...")]
[:div
[:h3 "Chuck quote of the day"]
[:em quote-text]])))
(defn quote-page []
(fn []
(do
(get-quote)
[:div
[:header
[render-quote]]
[:footer
[:p "footer here"]]])))
;; -------------------------
;; Initialize app
(defn mount-root []
(reagent/render [quote-page] (.getElementById js/document "app")))
(defn init! []
(mount-root))
I'll explain the relevant bits:
init will bootstrap the basics of the front-end, but in our case it's just calls mount-root which starts reagent telling it to call quote-page and placing the results in the DOM replacing the element with the ID of app.
quote-page calls get-quote which will call the API using the cljs-http library. I'm not checking for errors here, but basically when the request completes (either success or error) it will read the results from the channel (using <!) and place the response in response. The key is that response is a nested ClojureScript map that you can inspect to check if the result was successful or not. Note that I'm also printing the results with println instead of JS interop (.log js/console xxx) because console.log will show the inner details of how the nested map is implemented, which is not relevant for this case.
One the response is available, I store the results of the response in an atom called api-response. The key here is that the atom will contain nothing for a bit (while the request completes) and then the response will be inside it and reagent will take care of detecting the change and re-rendering.
Finally, quote-page calls render-quote which generates the markup for rendering the quote or a placeholder while it loads.
To run the whole thing, open a terminal and run lein run which will start a web server listening on port 3000 by default. In another terminal, run lein figwheel which will compile the ClojureScript code for you. One figwheel is ready it will start a REPL, and you can open the address http://0.0.0.0:3000/ in your computer to view the page.

ClojureScript - assoc is not working inside a promise

I have an array of art pieces. I want to find the route length and associate it with each art pieces.
My code will look like:
(defn load-art-routes [art-list ctx]
(doall (map-indexed (fn [index art]
(let [user-location (select-keys (:coords (sub> ctx :geolocation)) [:latitude :longitude])
art-location (:geoLocation art)]
(->> (map-request {:origin (str (:latitude user-location) "," (:longitude user-location))
:destination (str (:lat art-location) "," (:lon art-location))
:mode (name (sub> ctx :transition-mode))})
(p/map (fn [data]
(let [route-length (ocall js/Math "round" (/ (get-in data [:routes 0 :legs 0 :distance :value]) (* 0.621371192 1000)) 2)
route-duration (ocall js/Math "floor" (/ (get-in data [:routes 0 :legs 0 :duration :value]) 60))]
(js/console.log "load-art-routes route-length " route-length")
(assoc art :route-length route-length))))
(p/error (fn [error]
(util/log (str "GOOGLE DIRECTIONS API ERRORS" params) error)
))))) art-list))
art-list)
(defn map-request [params]
(when params
(let [endpoint google-directions-api-endpoint]
(->> (make-get-req (str endpoint "&" (encode-query-params params))
{})
(p/map (fn [data]
(util/log "GOOGLE DIRECTIONS API " data)
data))
(p/error (fn [error]
(util/log (str "GOOGLE DIRECTIONS API ERRORS" params ) error)
))))))
The route length calculation is correct but, assoc is not working. It is not actually associating it. I don't know what the issue is. Can anyone help me?
Please simplify this example! In the process, you will discover the problem.
First, update your question to include the require that shows what p/map, p/error, etc are. Also, put map-request before load-art-routes just like it must be in your source file.
Then, you should start by removing the thread-last ->> operator and use let with names for intermediate values:
(let [aa (map-request ...)
bb (p/map (fn [data] ...) aa)
cc (p/error (fn [error] ...) bb) ]
<bb or cc here?> )
My suspicion is that your p/error call is swallowing the results of p/map and returning nil.
This looks like you are trying to write "mutable" code.
Reformatting the code and fixing one error makes this more obvious:
(defn load-art-routes [art-list ctx]
(doall (map-indexed (fn [index art]
(let [user-location (select-keys (:coords (sub> ctx :geolocation)) [:latitude :longitude])
art-location (:geoLocation art)]
(->> (map-request {:origin (str (:latitude user-location) "," (:longitude user-location))
:destination (str (:lat art-location) "," (:lon art-location))
:mode (name (sub> ctx :transition-mode))})
(p/map (fn [data]
(let [route-length (ocall js/Math "round" (/ (get-in data [:routes 0 :legs 0 :distance :value]) (* 0.621371192 1000)) 2)
route-duration (ocall js/Math "floor" (/ (get-in data [:routes 0 :legs 0 :duration :value]) 60))]
(js/console.log "load-art-routes route-length " route-length)
(assoc art :route-length route-length))))
(p/error (fn [error]
(util/log (str " GOOGLE DIRECTIONS API ERRORS " params) error)
))))) art-list))
art-list)
load-art-routes simply returns the original art-list and kicks of some work in promises. These promises only update their versions of the list but given that we are using immutable data structures the returned art-list themselves remain unchanged. There is also a suspicious second art-list in the p/error call?
You'll probably want to restructure this to something that either returns a Promise or accepts a callback that will be called once all route-length have been computed.

Is there any way to make an onClick handler in Om without using anonymous function?

I want to make a click handler function for an Om component. The docs and Stack Overflow examples I've found always declare anonymous functions like this
(defn main-view [_ owner]
(reify
om/IRender
(render [_]
(let [xs (items)]
(dom/div nil
(om/build sub-view {:title "View A"})
(om/build sub-view {:title "View B"})
(dom/button
#js {:onClick
(fn [e] (om/transact! xs #(assoc % 1 {:text "zebra"})))}
"Switch To Zebra!"))))))
I think it's cleaner to declare click functions outside the jsx/template area, within the component, the way it's commonly done in regular React. Is there a way do this in Om within the component? I tried this, but it doesn't work because onClick is undefined:
(defn my-component []
(reify
om/IRender
(render [this]
; Using Sablono syntax
(html [:h1 "Here is a heading" {:on-click 'onClick} ]))
onClick
(onClick [this]
; this part never gets executed when you click
(.log js/console "click"))))
I'd like to avoid defining a separate function outside the component if it's possible.
Your question is sensible and it's about handling scope of data.
It is possible but the problem with this approach in most cases you will need local scope data from the outside code block (in your case, it's an Om component).
I would explain in code. Let's say you want to move handler function out:
(anything
(let [a 1 b 2]
(on-event (fn my-handler [evt] (log (+ a b (.someAttr evt)))))))
You'll end up with this which is way longer:
(defn local-data->handler [a b]
(fn [evt] (log (+ a b (.someAttr evt)))))
(anything
(let [a 1 b 2]
(on-event (local-data->handler a b))))
in case you just want to move around inside the component definition:
(anything
(let [a 1
b 2
my-handler (fn [evt] (log (+ a b (.someAttr evt))))]
(on-event my-handler)))
Please note: to keep event handler work, ensure your non-anonymous function (created with defn or let) is the same as the anonymous form, especially argument list.
onClick is undefined because you use it as if it's an Om protocol. Please consult Om lifecycle protocols for correct usage.
https://github.com/swannodette/om/wiki/Documentation
Per your requirements, you should move the function definition out of the component.
You should then be able to pass the function's name to the event listener:
(defn foo [] (println "foo"))
(defn my-component [text owner]
(reify
om/IRender
(render [_]
(dom/button
#js { :onClick foo }
"Click Here"))))

multipart/form-data support in ClojureScript

How can I submit multipart/formdata in ClojureScript? Is there any library that supports this? I can fallback to e.g. jquery.form.js but would prefer a plain ClojureScript solution.
I recently made a pull request to cljs-http to support form-data. Util r0man merges it, you can see instruction in my version's README at https://github.com/myguidingstar/cljs-http
Edited: The pull request has been merged. See the original repository instead.
This is how I did it:
(defn generate-form-data [params]
(let [form-data (js/FormData.)]
(doseq [[k v] params]
(.append form-data (name k) v))
form-data))
(defn upload [file]
(go (let [response (<! (http/post "http://localhost/upload"
{:body (generate-form-data {:file file})}))]
(prn (:status response))
(prn (:body response)))))
;; some-dom-element is a single file upload input
;; <input type="file">
(upload (-> some-dom-element .-files first))
If you don't want to use cljs-http, see cljs-http.core/request in its source code for how to make a direct call to XhrIo
https://github.com/r0man/cljs-http/blob/master/src/cljs_http/core.cljs
Take a look at cljs-http:
;; Form parameters in a POST request (simple)
(http/post "http://example.com" {:form-params {:key1 "value1" :key2 "value2"}})
;; Form parameters in a POST request (array of values)
(http/post "http://example.com" {:form-params {:key1 [1 2 3] :key2 "value2"}})
====== UPDATE =======
You'll need some iframe hack. Read this and this:
;; Imports
(:require [goog.events :as gev])
(:import [goog.net IframeIo]
[goog.net EventType]
(defn upload []
(let [io (IframeIo.)]
(gev/listen io
(aget goog.net.EventType "SUCCESS")
#(js/alert "SUCCESS!"))
(gev/listen io
(aget goog.net.EventType "ERROR")
#(js/alert "ERROR!"))
(gev/listen io
(aget goog.net.EventType "COMPLETE")
#(js/alert "COMPLETE!"))
(.setErrorChecker io #(not= "ok" (.getResponseText io)))
(.sendFromForm io (dom/by-id "form") "/upload")))

No more than 1024 pending put on a single channel

I'm not too familiar with core.async, but from what I've read, I know I can do stuff like this:
;; fetch data
(defn get-data-from-server
[]
(let [ch (chan)]
(fetch-data-from-server (fn [result]
(put! ch result)))
ch))
;; echo data
(go-loop []
(let [v (<! (get-data-from-server))]
(.log js/console v)))
Assume I need to get fresh data from server for every 1000ms, this is what I did:
(defn get-data-from-server
[]
(let [ch (chan)]
(.setInterval js/window
(fetch-data-from-server (fn [result]
(put! ch result))) 1000)
ch))
After some time it complains something like No more than 1024 pending put on a single channel, consider using ..... Any suggestion?
Why are you creating a brand new channel every time your go-loop runs? Perhaps you should not create the channel inside the go loop and only reference an already-created channel. That tends to be much more common and better performant.