I'm developing a single-page webapp using Boot. It originally started from the Holy Grail example.
My Compojure routes are defined like this:
(defroutes routes
; sente
(GET "/chsk" req ((:ring-ajax-get-or-ws-handshake (:sente system)) req))
(POST "/chsk" req ((:ring-ajax-post (:sente system)) req))
; everything else
(GET "/*" [] (-> (resource-response "/index.html")
(content-type "text/html")))
; vestige of old times
(route/not-found "Not Found"))
Note the star in the "/*" route: I want all requests to return index.html, with the ClojureScript part handling the actual routes.
I'm running the application in the dev mode, here's a snippet from build.boot:
(deftask dev
"Run a restartable system in the Repl"
[]
(comp
(environ :env {:http-port "3000"})
(watch :verbose true)
(system :sys #'dev-system :auto true :files ["handler.clj"])
(reload :on-jsload 'myapp.core/init!)
(cljs :source-map true)
(repl :server true :init-ns 'myapp.user)))
It works well for single-level paths, like localhost:3000/test. It breaks, though, for the pages with mult-level paths, e.g. localhost:3000/foo/bar.
The part of index.html that loads JavaScript looks like this:
<body>
<div id="container"></div>
<script type="text/javascript" src="/main.js"></script>
</body>
Originally it was src="main.js", but I've added the leading slash so it finds main.js from a multi-level page. Without the slash, the browser probably looks for the file in the directory [somewhere-in-cljs-output]/foo/main.js, assuming the page localhost:3000/foo/bar.
Now, main.js is generated by ClojureScript compiler, and here it is:
var CLOSURE_UNCOMPILED_DEFINES = null;
if(typeof goog == "undefined") document.write('<script src="main.out/goog/base.js"></script>');
document.write('<script src="main.out/cljs_deps.js"></script>');
document.write('<script>if (typeof goog != "undefined") { goog.require("boot.cljs.main26293"); } else { console.warn("ClojureScript could not load :main, did you forget to specify :asset-path?"); };</script>');
When it runs on a page localhost:3000/foo/bar, I see the warning.
Apparently the problem's the same as with the original main.js reference, in that the paths of required JavaScript files are relative, not absolute. How do I get them to be absolute?
I think I have to change the cljs task invocation in build.boot, but not sure how, because my mental model of how Boot works is very spotty.
Any help appreciated.
A little tinkering led to the following change in the dev task of build.boot:
(cljs :source-map true
:compiler-options {:asset-path "/main.out"})
Related
So I use re-graph version 0.1.11 and I try fetching data from the endpoint. After fetching data, I checked network tab in the browser and I found the expected data after that it should activate my callback function, but it doesn't seem to work (but sometimes it works and after refreshing the page a few times it doesn't work again). Here is the code.
;; how I init re-graph
(rf/dispatch [::re-graph/init
::conf/graphql-client-name
{:ws-url url
:http-url url
:ws-reconnect-timeout 500
:resume-subscriptions? true}])
(re-frame.core/reg-event-fx
::fetch-expected-data
(fn [cofx event]
(let [app-db (:db cofx)
some-params (-> event second (cljs.core/js->clj :keywordize-keys true))
token (-> app-db (lens/get-in (auth-db/lens-token :group-level-x)))]
(re-frame.core/dispatch
[:re-graph.core/query
::conf/graphql-client-name
"query findExpectedData($query: FetchExpectedDataInput!, $token: String!) {
findExpectedData(query: $query, token: $token){
value1
value2
...
}
}"
{:query some-params
:token token}
;; this is where the problem occurs
;; even though i found the data in the network tab, but
;; this callback doesn't seem to work (sometimes it works sometimes it doens't)
[::fetched-data-completed]]))))
(re-frame.core/reg-event-fx
::fetched-data-completed
(fn [cofx [_ {:keys [data errors] :as payload}]]
(let [app-db (:db cofx)
error-message (-> errors :errors first :message)]
(if (or (nil? errors) (empty? errors))
(do (bla bla when success))
(pr error-message)))))
I'm stuck with this problem for a few months. maybe because I fetch a lot of data at the same time? or could be something else anyone knows?. By the way the actual code I use defmacro, but it works the same way as the above code.
So I managed to find the answer to my own question. It seems like app-db has not been initialized properly so I fixed that problem and everything works fine. hope it helps someone who struggle with this problem.
I'm learning ClojureScript, I have two functions that just change the content in the "root-app" div:
(ns blog.core)
(defn mount-components []
(let [content (js/document.getElementById "root-app")]
(while (.hasChildNodes content)
(.removeChild content (.-lastChild content)))
(.appendChild content (js/document.createTextNode "Wilkommen zu mein
ekelhaft blog!!"))))
(defn init! []
(def current_url js/window.location.href)
(if (clojure.string/includes? current_url "about")
(.log js/console (str "Whatever URL ->>>" js/window.location.href))
(mount-components)))
All works fine in http://localhost:3000/about because the "root-app" div exists in that page, but in http://localhost:3000/blog, I get the error message:
Because there is no such div in that page. All this is weird because it looks like ClojureScript in fact finds that:
(if (clojure.string/includes? current_url "about")
is actually false en the console.log is not printed.
My question is: why the function mount-components runs and sends the error message even when the conditional if is false? The weird thing is that the console.log:
(.log js/console (str "Whatever URL ->>>" js/window.location.href))
doesn't run but mount-components function does. I guess I'm not understanding the "sequence" in the way that ClojureScript works.
I'm not sure, but by your description, I think the logic you are thinking of and the logic you are actually testing are not the same. Your if statement looks for the word 'about' in the URL. If it is there, then it prints the console log i.e. it will be there for http://localhost:300/about. If it is NOT there, it will run the mount-components function, which looks for the div ID which you say is not included on the page, so you get the error. the mount-components is an ELSE statement and is therefore executed when the test is false.
The if form works like (if cond true-branch false-branch), so your (mount-component) is executed because it's in the false branch. Check out when, which only has a true branch.
Figwheel displays the code bellow just fine. But I have to refresh the page to see any changes. What has to change for Figwheel to show changes? Is there a command to force redraw, without losing the application state?
BTW: Chrome has Disable Cache true and the CLJS icon appears when the file is saved.
(defn simple-example []
[ui/mui-theme-provider {:mui-theme (get-mui-theme
{:palette {:text-color (color :blue800)}})}
[:div
[ui/app-bar {:title "Hi all"
:icon-class-name-right "muidocs-icon-navigation-expand-more"}]
[ui/paper
[:div
[ui/tabs
[ui/tab {:label "Menu" :value "0"}
[:div "Hello world"]]]]]]]))
(defn ^:export run []
(render [simple-example]
(js/document.getElementById "app"))))
From the docs:
Setting :figwheel true or :figwheel { :on-jsload "example.core/reload-hook" } will automagically insert the figwheel client code into your application. If you supply :on-jsload the name of a function, that function will be called after new code gets reloaded.
An example reload hook plus configuration for Reagent:
(ns your-namespace.core
(:require [reagent.core :as r]))
(defn render [view]
(let [node (.getElementById js/document "app")]
(r/render-component view node)))
(defn rerender []
(let [node (.getElementById js/document "app")]
(r/unmount-component-at-node node)
(render [:div "Reloading"]))
(defn ^:export reload []
(rerender))
And then in your project.clj:
:cljsbuild {:builds {:dev {:source-paths ["src"]
:figwheel {:on-jsload "your-namespace.core/reload"}}}
/edit
Note that re-frame uses Reagent. In the case of re-frame I recommend starting with the re-frame-template. E.g.,
lein new re-frame your-project-name # options, e.g., +re-frisk +cider
This will give a default core.cljs as follows:
(defn dev-setup []
(when config/debug?
(enable-console-print!)
(println "dev mode")))
(defn mount-root []
(re-frame/clear-subscription-cache!)
(reagent/render [views/main-panel]
(.getElementById js/document "app")))
(defn ^:export init []
(re-frame/dispatch-sync [:initialize-db])
(dev-setup)
(mount-root))
The index.html has a node with id app and calls init. And the project.cljs specifies the on-jsload as follows:
:cljsbuild
{:builds
[{:id "dev"
:source-paths ["src/cljs"]
:figwheel {:on-jsload "your-project-name.core/mount-root"}
#_(...)}}
This should absolutely update the page with the component changed. If it does not do what you want I might have misunderstood your question.
Reagent needs to be notified about state changes to re-render the affected components on the screen. Your code does not yet have any inner state that can be watched in order to decide if a re-render is required.
You can store your app state in reagent atoms. When you dedreference a reagent atom in a reagent component (that is the simple-example component in your case) an event listener is set up to the state atom so that any time it changes the component will be re-rendered.
Put the following just before the definition of simple-example:
(defonce counter (reagent.core/atom 0))
(swap! counter inc)
This creates a state called counter if it does not exist yet. It also immediately increases it so any already registered components will be refreshed.
Then put a #counter deref call anywhere inside the function body of simple-example. This way the initial call of the function installs the state change listeners.
Now any time you modify the code the namespace get reloaded and thus counter atom increases triggering the re-render of your component.
I am using Rook framework for web services. I want to make API responses be pretty-printed. It seems that the response encoding is all handled by the wrap-restful-format function from ring.middleware.format. So I tried to replace the rook/wrap-with-standard-middleware function with my own version that passes different options through to ring.middleware.format.
(defn make-encoders-seq []
[(ring.middleware.format-response/make-encoder
(fn [s]
(json/generate-string s {:pretty true}))
"application/json")])
(defn wrap-with-standard-middleware-modified
[handler]
(-> handler
(ring.middleware.format/wrap-restful-format :formats [:json-kw :edn]
:response-options
[:encoders (make-encoders-seq)])
ring.middleware.keyword-params/wrap-keyword-params
ring.middleware.params/wrap-params))
(def handler (-> (rook/namespace-handler
["resource" 'my-app.resource])
(rook/wrap-with-injection :data-store venues)
wrap-with-standard-middleware-modified))
This compiles fine but it doesn't work to pretty print the responses, it seems like the custom encoder is never called.
Rook 1.3.9
ring-middleware-format 0.6.0
cheshire 5.4.0 (for json/generate-string in above)
Try to change your format/wrap-restful-format to:
(ring.middleware.format/wrap-restful-format :formats (concat (make-encoders-seq) [:edn])
Here is what i am trying to do, I can replace Dom with jQuery, but I don't know how to get the Dom value. That's shame.
Gist
This is a gist which I followed it.
So I ask the origin author, but no response yet.
PS: I just want to try some cljs in browser, so I didn't use any lib like jayq.
I have tried something like (.val "yes"), but it seems to be wrong.
(ns hello-world.jquerytest)
(enable-console-print!)
(def jquery (js* "$"))
(defn x []
(->
(jquery ".meat")
(.html "yes")))
(jquery
(fn []
(x)
(-> (jquery "li.numbers")
(.html "pink")
(.append "banana"))))
Here is my answer based on Vanessa's comment
I tried
(defn x []
(->
(jquery ".meat")
(.html)
(println)))
It will print the value which I want.
If I am using doto
It will print #<[object Object]> in console which I do not want.
(defn x []
(->
(doto
(jquery ".meat")
(.html)
(println))))