React HOC wrapping with Clojurescript and Reagent - clojurescript

I'm trying re-use the components from react-google-maps and implement the simple map example from the doc: https://tomchentw.github.io/react-google-maps/basics/simple-map
However, I'm a blocked by the withGoogleMap Higher-Order Component (HOC) that wraps the GoogleMap component. I have tried to adapt the classes with Reagent and use them as follow:
(def GoogleMap (adapt-react-class js/ReactGoogleMaps.GoogleMap))
(def withGoogleMap (adapt-react-class js/ReactGoogleMaps.withGoogleMap))
(defn Map [props]
[withGoogleMap
[GoogleMap props]])
in lieu of the following Javascript:
const Map = withGoogleMap(props => (
<GoogleMap
{... props}
>
</GoogleMap>
));
Without success. (I get the following error withGoogleMap(...): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.).

The withGoogleMap HOC is a function that should be called with the wrapped component. To implement the simple map example, you also need to provide props to the GoogleMap component to be wrapped. This can be achieved by adapting the component to reagent with adapt-react-class, implementing the CLJS component, and "converting back" with reactify-component before calling the HOC.
(ns simple-map.views
(:require [reagent.core :refer [adapt-react-class
create-element
reactify-component]]
react-google-maps))
(defn my-google-map []
(let [google-map (adapt-react-class react-google-maps/GoogleMap)]
[google-map {:defaultZoom 18
:defaultCenter {:lat 61.4481532
:lng 23.8608644}}]))
(defn MyMap [props]
(let [m (-> my-google-map
reactify-component
react-google-maps/withGoogleMap
adapt-react-class)]
[m props]))
(defn main-panel []
(let [container-element (create-element "div" #js{:style (clj->js {:height "768px" :width "1024px"})})
map-element (create-element "div" #js{:style (clj->js {:height "768px" :width "1024px"})})]
(fn []
[:div
[MyMap {:containerElement container-element
:mapElement map-element}]])))
Note that I am using the experimental :npm-deps support to require react-google-maps.

Related

Clojurescript advanced compilation unreachable-code

I have just added a new library(https://github.com/ptaoussanis/tempura/blob/master/README.md ) to my Clojurescript project.
WARNING: /matter/titan/source/titan-ui/out/taoensso/tempura/impl.js:96: WARNING - unreachable code}catch (e20422){if((e20422 instanceof Error)){
I also get this error :
Use of undeclared Var goog.structs/Map
(def ^:private coerce-xhr-params "Returns [<uri> <?data>]"
(let [url-encode
(fn url-encode
([params]
(when (seq params)
(-> params clj->js gstructs/Map. gquery-data/createFromMap .toString)))
^---
([uri params]
(let [qstr (url-encode params)
uri-with-query (if (str/blank? qstr) uri (str uri "?" qstr))]
[uri-with-query nil])))
Do I need to write an 'extern' for this? What does that look like?
It's hard to tell from your code and question details, but I assume you're having trouble with library call in advanced compilation mode.
The modern simple way is to skip externs thing altogether and just write calls/gets not in interop form, but by using https://github.com/binaryage/cljs-oops library.
You won't have to do anything else then - all your code will survive advanced compilation by default, no externs needed!
e.g. (.-nativeProp obj) would look like (oget obj "nativeProp"), and ocall is used for function calls correspondingly.

Why do js functions fail when I assign them to a local variable?

In clojurescript 1.9.89 and Chrome 50.0.2661.102, I can create a log statement like:
(js/console.log "works")
But I can't create one like:
(def brokenlog js/console.log)
(brokenlog "but not here")
--> #object[TypeError TypeError: Illegal invocation]
When I try to compare these approaches by printing the value of my own brokenlog function, it looks just like the "real one" -- that is, both brokenlog and js/console.log evaluate for me as:
#object[log "function log() { [native code] }"]
Similarly, I see this behavior with:
cljs.user=> (js/document.querySelector "body")
#object[HTMLBodyElement [object HTMLBodyElement]]
cljs.user=> (def l js/document.querySelector)
#'cljs.user/l
cljs.user=> (l "body")
#object[TypeError TypeError: Illegal invocation]
nil
Upgrading to Chrome 52 fixes the console.log behavior, but not the document.querySelector behavior.
So I have two questions:
1. What am I missing
2. Where are the official docs I should be reading that would explain it?
Thanks!
Which browser and clojurescript version you are using?
the following code should work on your browser if it display nil into box.
.as-console-wrapper .as-console {
display: none;
}
<pre><code class="language-klipse">
(js/console.log "Work!")
(def brokenlog js/console.log)
(brokenlog "Work again!")
; two line should be seen in your browser log
</code></pre>
<script>
window.klipse_settings = {
selector: '.language-klipse', // css selector for the html elements you want to klipsify
};
</script>
<script src="http://app.klipse.tech/plugin/js/klipse_plugin.js?"></script>
<link href="http://app.klipse.tech/css/codemirror.css" rel="stylesheet"/>
EDIT
Here is the clojurescript
(ns hello-world.core)
(def mylog js/console.log)
(mylog "Hello")
compiled to javascript
hello_world.core.mylog = console.log;
hello_world.core.mylog.call(null,"Hello");
console.log.call(null, ....) trigger the chrome bug, console.log expecting this is the console object. It should be fixed as mentioned in the issue log https://bugs.chromium.org/p/chromium/issues/detail?id=167911.

how to extend elixir phoenixframework html form helper

I tryed to extend Phoenix.HTML.Form module of the phoenixframework. My intension is to wrap the html form helper text_input to create a text input field for a timex date value to use it with bootstrap-datepicker.
I'm new to elixir but I read about protocols to extend elixir modules. So I tryed:
defprotocol Phoenix.HTML.Form.Extension do
def timex_date_input(form, field, opts \\ [])
end
defimpl Phoenix.HTML.Form.Extension, for: Phoenix.HTML.Form do
def timex_date_input(%{model: model, params: params}, field, opts \\ []) do
# my logic goes here
form = %{model: model, params: params}
text_input(form, field, opts)
end
end
But, it doesn't work, because: "function text_input/3 undefined". What would be the right solution?
You need to import the Phoenix.HTML.Form module to be able to use text_input - you will find that this is already imported into your views (and your templates since they are functions in your views) in your web.ex file.
If you wish to add a new form function, you can simply define the function (there is no need for protocols - these are often used as a way to extend libraries - phoenix_ecto is a great example of this):
defmodule MyApp.FormHelpers
def timex_date_input(form, field, opts \\ []) do
# my logic goes here
form = %{model: model, params: params}
Phoenix.HTML.Form.text_input(form, field, opts)
end
Then you can either import this into your view (import MyApp.FormHelpers), or use the full function name in your template:
<%= timex_date_input(f, :date, ...) %>

Calling a ".done" JS callback

I'm having some trouble with wrapping a JS library because I can't get a .done callback to work correctly. In JavaScript the code would look like this:
db.values("inventory").done(function(item) {
console.log(item);
});
So I tried a couple (very dirty) ClojureScript methods to translate this:
(defn log []
(console/log "working?"))
(defn stock []
(#(.done % log) (.values db "inventory")))
and
(defn stock []
(js* "db.values('inventory').done(function(item) {
console.log(item);
})"))
but neither of these worked. The error message is always something like: db.values(...).done is not a function
Are there any protocol extensions (or anything else) that could be used here to cover the JS callback? Otherwise, can goog.async.Deferred somehow intercept the callback again?
maybe this helps you!
I've done from node but it must work from browser with a little details
First for the demo code I've prepared a mock js lib to simulate yours (my_api.js)
This is my_api.js
console.log("my_api.js");
var db={};
db.item="";
db.values=function(_string_){
db.item="loadign_"+_string_;
return this;
};
db.done=function(_fn_){
_fn_(db.item);
};
var api={hello:"ey ", db:db};
module.exports=api;
// your pretended chain calls
// db.values("inventory").done(function(item) {
// console.log(item);
// });
And from clojurescript code ...
(ns cljs-demo.hello)
(defn example_callback []
(let [my-api (js/require "./my_api") ; the api.js lib used for this example
db (aget my-api "db") ; this is your db object
my_fn (fn [item] ;this is your callback function
(println "printing from clojurescript" item)
)
]
(do
(-> db (.values "inventory") (.done my_fn)) ;; calling your js in a similar way you want in js
;; or
(.done (.values db "inventory_bis") my_fn) ;; calling nested form in the standar lisp manner
)
)
)
(set! *main-cli-fn* example_callback) ;default node function
And from console (node.js)
node your_output_node.js
And you'll obtain
printing from clojurescript loadign_inventory
printing from clojurescript loadign_inventory_bis
Good Luck,
Juan

Using hunchentoot to parse post request sent by model.save() in Backbone.js

I am a javascript/web application newbie and trying to implement my first web application using hunchentoot and backbone.js. The first thing I was experimenting is to understand how model.fetch() and model.save() work.
It seems to me that model.fetch() fires a "GET" request and model.save() fires a "POST" request instead. Therefore, I wrote a easy-handler in hunchentoot as below:
(hunchentoot:define-easy-handler (dataset-handler :uri "/dataset") ()
(setf (hunchentoot:content-type*) "text/html")
;; get the request type, canbe :get or :post
(let ((request-type (hunchentoot:request-method hunchentoot:*request*)))
(cond ((eq request-type :get)
(dataset-update)
;; return the json boject constructed by jsown
(jsown:to-json (list :obj
(cons "length" *dataset-size*)
(cons "folder" *dataset-folder*)
(cons "list" *dataset-list*))))
((eq request-type :post)
;; have no idea on what to do here
....))))
This is designed to handle the fetch/save of a model whose corresponding url is "/dataset". The fetch works fine, but I got really confused by the save(). I saw the "post" request fired and handled by the easy-handler, but the request seems to have only a meaningful header, I cannot find the actual json object hidden in the request. So my question is
How can I get the json object out of the post request fired by model.save(), so that later json library (e.g., jsown) can be used to parse it?
What should hunchentoot reply in order to let the client know that the "save" is successful?
I tried "post-parameters" function in hunchentoot and it returns nil, and didn't see many people using hunchentoot+backbone.js by googling. It is also helpful if you can direct me to some articles/blog posts that help understanding how the backbone.js save() works.
Thank you very much for your patience!
Thanks to wvxvw's comments, I found the solution to this question. The json object can be retrieved by calling hunchentoot:raw-post-data. To be more detailed, we first call (hunchentoot:raw-post-data :force-text t) to get the post data as a string, and then feed it to jsown:parse. A complete easy-handler is shown below:
(hunchentoot:define-easy-handler (some-handler :uri "/some") ()
(setf (hunchentoot:content-type*) "text/html")
(let ((request-type (hunchentoot:request-method hunchentoot:*request*)))
(cond ((eq request-type :get) ... );; handle get request
((eq request-type :post)
(let* ((data-string (hunchentoot:raw-post-data :force-text t))
(json-obj (jsown:parse data-string))) ;; use jsown to parse the string
.... ;; play with json-obj
data-string))))) ;; return the original post data string, so that the save() in backbone.js will be notified about the success.
Hope this helps others who have the same confusion.