Adding a Clojurescript protocol to a Google Closure element - clojurescript

How can i extend a Google Closure element using Clojurescript protocols. I tried this but it doesn't seem to work:
(ns my-stuff.main
(:require
[goog.dom :as dom))
(defprotocol ds
(-set-text [this text]))
(extend-type js/HTMLDivElement
ds
(-set-text
[this text] (dom/setTextContent this text)))
(set-text (.getElementById js/document "a") "howdy")
: I must be doing something basic wrong I think as the element with ID "a" on my HTML page never gets updated.

I was working too late and didn't spot the missing - (minus sign):
(ns my-stuff.main
(:require
[goog.dom :as dom))
(defprotocol ds
(-set-text [this text]))
(extend-type js/HTMLDivElement
ds
(-set-text
[this text] (dom/setTextContent this text)))
(-set-text (.getElementById js/document "a") "howdy")

Related

Routing using default template in reagent

I am trying to use reagent to build my very basic project but I have a problem with routing and its parameter. This is from reagent looks like
EDITED - :require s added
(ns hammerslider.core
(:require [reagent.core :as reagent :refer [atom]]
[secretary.core :as secretary :include-macros true]
[accountant.core :as accountant]))
;; Views
(defn home-page []
[:div [:h2 "Welcome to hammerslider"]
[:div [:a {:href "/c/12"} "go custom"]]])
(defn c [test]
[:div [:h2 (str "on C " test)]
[:div [:a {:href "/"} "go to the home page"]]])
I am trying to get 12 from c route which is the route handling is look like this
(def page (atom #'home-page))
(defn current-page []
[:div [#page]])
(secretary/defroute "/" []
(reset! page #'home-page))
(secretary/defroute "/c/:test" [test]
(reset! page #'c)
I'm trying to catch the test parameter with the view function but it appears on C, not on C 12. How do I get to transfer the test parameter in to the view of c? or should I save it on different atoms?
EDITED - Mine solved by saving parameters into atom and it works, but is it the right way to pass the parameter?
(def parameter (atom ()))
(defn c []
[:div [:h2 (str "on C " (:test #parameter))]
[:div [:a {:href "/"} "go to the home page"]]])
(secretary/defroute "/c/:test" {:as params}
(do (js/console.log params)
(reset! parameter params)
(reset! page #'c)
))
It is depended on how you use your route parameters. The only guarantee between your program and reagent is if the value in ratom changed, the reagent component will be changed accordingly.
The TodoMVC is quite feature completed example for you to use reagent and secretary.
https://github.com/tastejs/todomvc/blob/gh-pages/examples/reagent/src/cljs/todomvc/routes.cljs
By the way, most of the time I will use re-frame instead of using reagent directly.

Why does Reagent render JSON in three ways?

I am trying to render JSON data from an API call in Clojurescript/Reagent. When I use js/alert I see the json I expect: ["Sue" "Bob"]
(defn- call-api [endpoint]
(go
(let [response (<! (http/get endpoint))]
(:names (:body response)))))
;; -------------------------
;; Views
(defn home-page []
[:div (call-api "/api/names")])
This is how I'm referencing the libraries (in case there's an problem there).
(ns myapp.core
(:require [reagent.core :as reagent :refer [atom]]
[reagent.session :as session]
[cljs-http.client :as http]
[cljs.core.async :refer [<! >!]]
[secretary.core :as secretary :include-macros true]
[accountant.core :as accountant])
(:require-macros [cljs.core.async.macros :refer [go]]))
But when I log it to the console, I get a long hash that looks nothing like the API response. The browser renders "00000000000120".
Why do these results differ? (browser, alert window, console message)
How can I get what I'm seeing in the alert window to render on the page?
When you call call-api it is going to return a go block. Instead of trying to consume that go block directly in your Reagent function, you could instead update the return value in a ratom.
(def app-state (atom)) ;; ratom
(defn- call-api [endpoint]
(go
(let [response (<! (http/get endpoint))]
(reset! app-state (:names (:body response))))))
(defn home-page []
[:div #app-state])
(defn main []
(call-api))

Syntax for giving a button a particular width

How do you set a button to a particular width? This is one of the things I have tried so far:
(:require [om.next :as om :refer-macros [defui]]
[om.dom :as dom])
(defui HelloWorld
Object
(render [this]
(dom/button #js {:style {:width 300}} (get (om/props this) :title))))
Setting the title of the button works fine and is probably not relevant for this question. I've left it in because it is a typical thing to be doing, and placement of the attributes might be important.
The lein project.clj file has these dependencies:
[org.clojure/clojure "1.7.0"]
[org.clojure/clojurescript "1.7.170"]
[org.omcljs/om "1.0.0-alpha24"]
[figwheel-sidecar "0.5.0-SNAPSHOT" :scope "test"]
I think the problem is due to #js only working on the top level. #JS will work on a top level map {} or vector [], but if you have nested data as values, you need to include additional #js calls for each embedded object.
What you really need is
(:require [om.next :as om :refer-macros [defui]]
[om.dom :as dom])
(defui HelloWorld
Object
(render [this]
(dom/button #js {:style #js {:width 300}} (get (om/props this) :title))))
Have a look at this post on using #js. For readability, rather than nested #js calls, you are often better off using clj->js
I got it to work with this:
(defui HelloWorld
Object
(render [this]
(dom/button (clj->js {:style {:width 300}}) (get (om/props this) :title))))
Note the use of clj->js.

How to create Material UI component in Om Clojurescript?

First of all, this https://github.com/taylorSando/om-material-ui doesn't work with latest React/Material UI.
The main reason, I think, is this warning in console:
Warning: Something is calling a React component directly. Use a factory or JSX instead. See: https://fb.me/react-legacyfactory
I've also tried to create component "manually":
(ns om-test.core
(:require [om.core :as om :include-macros true]
[om-tools.dom :as dom :include-macros true]
[om-tools.core :refer-macros [defcomponent]]
[om-material-ui.core :as mui :include-macros true]))
(enable-console-print!)
(defonce app-state (atom {:text "Hello Chestnut!"}))
(defn main []
(om/root
(fn [app owner]
(reify
om/IRender
(render [_]
(dom/div (dom/element js/MaterialUI.Paper {} "Hello")
(mui/paper {} "Hello"))
)))
app-state
{:target (. js/document (getElementById "app"))}))
So, both of these approaches produces same warning above.
There has been obviously some changes with React. It suggests to create components programatically as:
var React = require('react');
var MyComponent = React.createFactory(require('MyComponent'));
function render() {
return MyComponent({ foo: 'bar' });
}
So how do I create Material UI component inside Om render function, or maybe better How do I create React component inside Om render function, in general?
By Material UI I mean this https://github.com/callemall/material-ui
My dependencies
:dependencies [[org.clojure/clojure "1.6.0"]
[org.clojure/clojurescript "0.0-3058" :scope "provided"]
[ring "1.3.2"]
[ring/ring-defaults "0.1.4"]
[compojure "1.3.2"]
[enlive "1.1.6"]
[org.omcljs/om "0.9.0"]
[environ "1.0.0"]
[http-kit "2.1.19"]
[prismatic/om-tools "0.3.11"]
[om-material-ui "0.1.1" :exclusions [org.clojure/clojurescript
org.clojure/clojure]]]
Okay I eventually figured out.
Build latest version of Material UI with this: https://github.com/taylorSando/om-material-ui/tree/master/build-mui. Note: No need to build CSS in current version (0.10.4)
Include built material.js into your HTML file. Again, no need to include CSS.
Avoid loading React twice https://github.com/taylorSando/om-material-ui#avoid-loading-react-twice
Now the code for Om:
(ns material-ui-test.core
(:require [om.core :as om :include-macros true]
[om.dom :as dom :include-macros true]))
(enable-console-print!)
(defonce app-state (atom {:text "Hello Chestnut!"}))
(def ^:dynamic *mui-theme*
(.getCurrentTheme (js/MaterialUI.Styles.ThemeManager.)))
(defn main []
(om/root
(fn [app owner]
(reify
om/IRender
(render [_]
(let [ctor (js/React.createFactory
(js/React.createClass
#js
{:getDisplayName (fn [] "muiroot-context")
:childContextTypes #js {:muiTheme js/React.PropTypes.object}
:getChildContext (fn [] #js {:muiTheme *mui-theme*})
:render (fn []
(dom/div nil
(dom/h1 nil (:text app))
(js/React.createElement js/MaterialUI.Slider)))}))]
(ctor. nil)))))
app-state
{:target (. js/document (getElementById "app"))}))
If you used just (js/React.createElement js/MaterialUI.Slider) without :getChildContext etc. it would throw error:
Uncaught TypeError: Cannot read property 'component' of undefined
This is because of how current MaterialUI works. Read "Usage" part here: http://material-ui.com/#/customization/themes
Code for Reagent is bit more elegant. But I've used here namespace
[material-ui.core :as ui :include-macros true]
copy-pasted from this example project: https://github.com/tuhlmann/reagent-material
(def ^:dynamic *mui-theme*
(.getCurrentTheme (js/MaterialUI.Styles.ThemeManager.)))
(defn main-panel []
(let [active-panel (rf/subscribe [:active-panel])]
(r/create-class
{:display-name "Main Panel"
:child-context-types
#js {:muiTheme js/React.PropTypes.object}
:get-child-context
(fn [this]
#js {:muiTheme *mui-theme*})
:reagent-render
(fn []
[ui/Slider {:name "slide1"}])})))
EDIT: I released library, which greatly simplifies whole process.
Library: https://github.com/madvas/cljs-react-material-ui
Example app: https://github.com/madvas/cljs-react-material-ui-example
I'm not using Material UI but React Widgets. Here is the wrapper I needed to write for om:
(defn dropdown-list
[data owner {:keys [val-key menu-key id-key label-key props]}]
(reify
om/IRender
(render [_]
(let [menu (-get data menu-key)]
(js/React.createElement js/ReactWidgets.DropdownList
(-> {:defaultValue (-> (find-by-key menu id-key (-get data val-key))
(-get label-key))
:data (mapv #(-get % label-key) menu)
:onChange (fn [new-val]
(let [new-id (-> (find-by-key menu label- key new-val)
(-get id-key))]
(om/update! data val-key new-id)))}
(merge props)
clj->js))))))
So, in general, you need to get the React class (js/ReactWidgets.DropdownList) and call js/Readt.createElement while passing the props on render.

How can I use **require** in ClojureScript?

I ClojureScript I can do something within a ns declaration like like:
(ns demo2
(:require
[goog.ui.Textarea :as text-area]
)
)
: but :
(require '[goog.ui.Textarea :as text-area])
: doesn't work. Does anyone know what I'm doing wrong?
There is no (require) definition in ClojureScript.
As stated in the wiki page on namespaces:
You must use the :as form of :require