I'm attempting to use macros to generate a series of similar Om components (e.g. a modal element which contains common boilerplate and a dynamic "body").
I've got the solution below mostly working. The one exception is accessing the appropriate owner in the form's on-submit event handler.
;; macros.clj
(defmacro build-modal
([disp-name body]
`(fn [_# owner#]
(reify
om.core/IRender
(~'render [_#]
(dom/div {:class "login-view-modal"}
(dom/div {:class "login-view-modal-backsplash"})
(dom/div {:class "login-view-modal-content"}
~#body))))) nil))
;; ui.cljs
(defn login-view-modal []
(bs-macros/build-modal
"login-modal"
'(dom/form {:on-submit (fn [event]
(.preventDefault event)
(let [username (get-value owner "username")
password (get-value owner "password")
data {:email_address username
:password password}]
(handle-login-view-modal-form-submit data)))}
;; elided for brevity
(dom/div
(dom/button "LOG IN")))))
(defcomponent home-page-view [app owner]
(init-state [_] {:text ""})
(render-state [this state]
;; elided for brevity
(dom/div (login-view-modal))))
The modal is rendered as expected, however when submitting the form I'm presented with the error: Uncaught TypeError: Cannot read property 'getDOMNode' of undefined, which seems to be because owner is not in scope. (Note, this element and its event handler functions as expected when constructed in full - i.e. without using a macro.)
How do I make the appropriate owner available to the body of the macro?
You need variable capture for this to work. Instead of gen-syming via owner# which will generate a unique symbol, just inline ~'owner wherever you want to refer to it.
Related
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 have the following ClojureScript code to make a POST request:
(defn perform-post [resource]
"Performs a post and returns the body :)"
(go (let [response (<! (http/post resource))]
(:body response))))
When I make a call to a resource which returns a number
(js/console.log (perform-post post-create-recipe-url))
This prints:
bufObject { buf={...}, n=1, cljs$lang$protocol_mask$partition0$=2,
more...}
bufObject { head=1, tail=0, length=1, meer...}
arr
["6276677237104933520", undefined]
I want to obtain the "6276677237104933520" (the post body) information as a "return" value.
How can I accomplish this? I tried <!! but it does not work since it is not defined.
Blocking semantics (<!!) is not available on ClojureScript platform.
You can retrieve value from a channel only within go block:
(go (js/console.log (<! (perform-post post-create-recipe-url))))
om "0.8.0"
I have recently started learning om by using examples codes of om repository. Now, I am checking multi example and I can understand the behavior of this program.
After I clicked '+' button,
First, "Even(or Odd) widget unmounting" is printed.
Next, "Odd(or Even) widget mounting" is printed.
But when I added following code
(just change even-odd-widget defmulti code to defn code)
(defn test-widget
[props owner]
(reify
om/IWillMount
(will-mount [_]
(println "Test widget mounting"))
om/IWillUnmount
(will-unmount [_]
(println "Test widget unmounting"))
om/IRender
(render [_]
(dom/div nil
(dom/h2 nil (str "Test Widget: " (:my-number props)))
(dom/p nil (:text props))
(dom/button
#js {:onClick #(om/transact! props :my-number inc)}
"+")))))
and tried to use this function instead of test-widget, as the result, there was no print message...
So what is the difference between defmulti and defn in this case?
Is this bug or correct behavior?
Thanks in advance.
The protocol methods om/IWillMount and om/IWillUnmount are only invoked once when the component is mounted/unmounted. It is not invoked on every render and therefore a log message will not be produced when you click the '+' button if it does not result in a component being mounted/unmounted. The use of multimethods has no impact on this behavior.
The reason that you are seeing repeated log statements when using the multimethod version is that different components are being returned on every click and therefore the components are also mounted/unmounted every time you click '+', while with the plain function a single component is mounted and remains mounted every time you click '+'.
The log messages you are producing will appear in the developer console of the browser for both your plain function and multimethod versions, but only when the component mounts/umounts and not on every render.
Clicking the '+' button will trigger a re-render in the existing mounted component in the plain function scenario. If you want to log on every render of the component, you would need to do the following:
(defn test-widget
[props owner]
(reify
om/IWillMount
(will-mount [_]
(println "Test widget mounting"))
om/IWillUnmount
(will-unmount [_]
(println "Test widget unmounting"))
om/IRender
(render [_]
(.log js/console "Rendering test widget!")
(dom/div nil
(dom/h2 nil (str "Test Widget: " (:my-number props)))
(dom/p nil (:text props))
(dom/button
#js {:onClick #(om/transact! props :my-number inc)}
"+")))))
(defn app [props owner]
(reify
om/IRender
(render [_]
(apply dom/div nil
(om/build-all test-widget (:widgets props))))))
This code is from the default luminus template:
(deftype RenderableTemplate [template params]
Renderable
(render
[this request]
(content-type
(->>
(assoc
params
(keyword (s/replace template #".html" "-selected"))
"active"
:servlet-context
(:context request)
:user-id
(session/get :user-id)
:user
(session/get :user))
(parser/render-file (str template-path template))
response)
"text/html; charset=utf-8")))
(defn render [template & [params]]
(RenderableTemplate. template params))
And I need to test this function using clojure.test:
(defn home-page [& [user]]
(layout/render
"home.html"
{:user user}))
How will I test the above function with the value associated to the key :user?
I suggest you to read some documentation and make a reasonable effort before launching so general questions. This could be an start http://blog.jayfields.com/2010/08/clojuretest-introduction.html
After you feel a bit comfortable with clojure test you may like to move to https://github.com/marick/Midje/wiki/A-tutorial-introduction-for-Clojure.test-users
Enjoy :)
First set your routes and handlers like this:
(defn home-page [& [user]]
(layout/render
"home.html"
{:user user}))
; setting routes
(defroutes main-routes
(GET "/user/:user" [user] (home-page user)))
Then do the unit testing:
Basic unit test
(deftest home-page-test
; Check if response code is 200
(is (= 200 (:status (main-routes {:request-method :get :uri "/user/Michael"})))))
or you can also use Midje
Using Midje
(:use midje.sweet
clojure.test)
(fact "Homepage Test"
(:status (main-routes {:request-method :get :uri "/user/Michael")) => 200)
I faced the same problem.
Calling (home-page) directly will return a RenderableTemplate type which isn't useful for testing.
In your test require:
(:require [capacityplanning.layout :as layout]
[selmer.parser :as parser])
Add this function:
(def template-path "templates/")
(defn mockRender [template params]
(parser/render-file (str template-path template) params))
And in your test you can bind:
(with-redefs [layout/render mockRender]
(home-page user))
Now when home-page is called it will return the html as a string. This should be more useful to write unit tests against.
I'm using an AngularJS resource to basically $.ajax() some form data.
Post.put({user:$scope.getLoggedInUser(), action:"new"},{body:$scope.postBody, "public":$scope.postPublic}, function(post) {
On the form is a checkbox named "public."
I am using this function to merge the form data with the params from the URL:
(defn get-params [request]
(merge (:params request) (parse-string (slurp (request :body)) true)))
when I println from my route's handler like so I get a nice true when the checkbox is checked:
(println (:public (get-params request)))
However, when I pass the parameter (i.e. not the println) to another function in my controller that talks to the database, I do another println at the beginning of that function and get nil instead.
I've tried passing it as
(read-string x)
(boolean (Boolean/valueOf x))
(Boolean/valueOf x)
to no avail.
One thing that might be causing it (but I don't know why) is that I'm wrapping the request through authentication like this:
(auth? request #(create x y z))
where the (create) function creates a record in the database.
I can't get it to be true no matter what I've tried.
EDIT: more complete code
CONTROLLER
(defn auth? [request callback-true & [callback-false]]
(println (callback-true))
(let [login-response (auth/login request)]
(if (and (not (string? login-response))
login-response)
(callback-true)
(if callback-false
(callback-false)
(json-response (str "{\"auth\":" login-response "}"), 401)))))
(defn create [user logged-in-user body public?]
(if (= logged-in-user user)
(json-response (post-view/single-post (post/create user body public?)))
(json-response "{\"auth\":\"can't create post under different logged in user\"}" 401)))
(defroutes routes
....
(PUT "/api/:user/new" request
(println request)
(auth? request
#(create (:user (request :params))
(:user (request :session))
(:body (get-params request))
(:public (get-params request)))))
....
)
MODEL
(defn create [username body public?]
(println public?)
(when-not (and
(str/blank? body)
(str/blank? username))
(let [user-id (:id (get-id-from-username username))
new-post-id
(:id
(sql/with-connection db
(sql/insert-values :post
[:usr_id :body :public] [user-id body (Boolean/valueOf public?)])))]
(by-id new-post-id))))
Don't call get-params twice. (request :body) returns a stream, which can't be read twice.