Serving data with Ring and Compojure - json

I am configuring and setting up a web application to serve the JSON data http://www.ericrochester.com/clj-data-analysis/data/census-race.json file statically.
My dependencies:
:dependencies [[org.clojure/clojure "1.5.1"]
[org.clojure/clojurescript "0.0-2197"]
[ring/ring-core "1.1.7"]
[ring/ring-jetty-adapter "1.1.7"]
[compojure "1.1.3"]
[hiccup "1.0.2"]
[lein-cljsbuild "0.2.10"]]
As the title says I'm using Ring as a development plugin, i.e.
:plugins [[lein-ring "0.8.3"]]
The Leiningen project is
(ns test-app.core
(:require [compojure.route :as route]
[compojure.handler :as handler]
[clojure.string :as str])
(:use compojure.core
ring.adapter.jetty
[ring.middleware.content-type :only
(wrap-content-type)]
[ring.middleware.file :only (wrap-file)]
[ring.middleware.file-info :only
(wrap-file-info)]
[ring.middleware.stacktrace :only
(wrap-stacktrace)]
[ring.util.response :only (redirect)]))
and in the project.clj
:ring {:handler test-app.core/app}
which I am not sure if this will tell Ring where the web application
is.
Moreover, I am not sure how to serve the JSON data file statically. I have read that "Ring serves static files out of the /resources directory of your project. In this case, create the directory /resources/data and put the data file that you downloaded from http://www.ericrochester.com/clj-data-analysis/data/census-race.json into it."
It is mostly this last part about creating the /resources/data directory where I am lost in the implementation. Could someone show how this part is done?
If I can learn to get past this I am looking to build routes and handlers, i.e.
(defroutes site-routes
(GET "/" [] (redirect "/data/census-race.json"))
(route/resources "/")
(route/not-found "Page not found"))
and
(def app
(-> (handler/site site-routes)
(wrap-file "resources")
(wrap-file-info)
(wrap-content-type)))

and in the project.clj
:ring {:handler test-app.core/app}
which I am not sure if this will tell Ring where the web application
is.
When you run $ lein ring server-headless it will look for app in the test-app.core namespace. You should have something like the following in your core.clj file:
(def app
(handler/site app-routes))
It is mostly this last part about creating the /resources/data
directory where I am lost in the implementation. Could someone show
how this part is done?
In the root of your project directory you should have a resources folder. Create a data folder inside the resources folder.
I'm not sure what problem you're having?

If your project looks like this:
project.clj
src/test_app/core.clj
resources/public/data/census-race.json
Then the your site-routes handler will serve up that JSON file when you request the path /data/census-race.json.
You don't need any of the extra middleware like wrap-file, wrap-file-info, or wrap-content-type, since compojure.route/resources already does everything you need.

Related

Failed to construct ‘HTMLElement’: Please use the ‘new’ operator

I am trying to use AWS Amplify Authentication lib in a re-frame app.
The lib provides a higher order component withAuthenticator which is supposed to wrap the main view of your app. I am trying to use reactify-component and adapt-react-class but unfortunately I get the following error:
Uncaught TypeError: Failed to construct ‘HTMLElement’: Please use the ‘new’ operator, this DOM object constructor cannot be called as a function.
(defn main-panel []
[:div
[:h1 "Hello" ]])
(def root-view
(reagent/adapt-react-class
(withAuthenticator
(reagent/reactify-component main-panel))))
(defn ^:dev/after-load mount-root []
(re-frame/clear-subscription-cache!)
(aws-config/configure)
(re-frame/dispatch-sync [::events/initialize-db])
(reagent/render [root-view]
(.getElementById js/document "app")))
Any help is appreciated
I had this issue with reagent + amplify.
Solved it with 2 changes, but I'm unsure if both are needed
#1 Change output of the google closure compiler to es6 (or higher). Amplify seems to use es6 features that Cannot be polyfilled.
This is for shadow-cljs (shadow-cljs.edn), but this should be possible for other build systems as well.
{:builds {:app {:compiler-options {:output-feature-set :es6}}}}
Disclaimer: I switched to shadow-cljs from lein-cljsbuild since I could not get lein-cljsbuild to respect the configuration for es6 output.
#2 Use functional components.
In reagent 1.0.0-alpha1 and up you can change the compiler to produce functional components by default.
(ns core
(:require
[reagent.core :as r]))
(def functional-compiler (r/create-compiler {:function-components true}))
(r/set-default-compiler! functional-compiler)
There are other ways to make functional components. Check the documentation if you don't like, or can't use, this approach.

Clojurescript error with cljs-http GET request for JSON file - badly formed

Beginner Clojurist here. I'm trying to parse a JSON file using Clojurescript and the cljs-http library. I have strange behaviour using the following function:
(defn make-remote-call [endpoint]
(go (let [response (<! (http/get endpoint))]
(js/console.log (:body response)))))
This will print the json file to the console but I'll get this error message:
XML Parsing Error: not well-formed
Location: file:///***U2328710-data.json
Line Number 1, Column 1:
Things I've tried:
the JSON file passes http://jsonlint.com with success, but https://jsonformatter.curiousconcept.com/ parses the file and says Error:Invalid encoding, expecting UTF-8, UTF-16 or UTF-32.[Code 29, Structure 0]
same issue when I deploy on Apache server. My .htaccess file is correctly set up to send content-header to application/json and charset to utf-8 (though I've read I should be sending UTF-8 in caps, haven't been able to do that)
I can parse an XML file with the same function without a problem
I can parse the same JSON file without a problem using the deprecated js/XMLHttpRequest
Running out of ideas - can someone help please? I wonder if cljs-http doesn't understand it's a json file, can I force it / maybe override headers? Thanks,
I think it's either some encoding problem in your JSON file or some other problem outside of the cljs-http library. I ran a small test using a new project created with lein new figwheel json-client and added the dependency on [cljs-http "0.1.46"] to project.clj
For a valid JSON file, I went to https://api.github.com/users/clojure/repos and saved the contents as resources/public/repos.json inside the project folder.
The contents of my core.clj file are:
(ns json-client.core
(:require-macros [cljs.core.async.macros :refer [go]])
(:require [cljs-http.client :as http]
[cljs.core.async :refer [<!]]))
(enable-console-print!)
(defn make-remote-call [endpoint]
(go (let [response (<! (http/get endpoint))]
(js/console.log (clj->js (:body response)))))) ;; NOTE: cljs->js
(defonce app-state (atom {:text "Hello world!"}))
;; This content is served by figwheel, configured in project.clj
(make-remote-call "http://0.0.0.0:3449/repos.json")
(defn on-js-reload []
;; optionally touch your app-state to force rerendering depending on
;; your application
;; (swap! app-state update-in [:__figwheel_counter] inc)
)
Note: there's only one change in the line that logs to the console (uses clj->js) but that's all.
... when I launch the project with lein figwheel it takes a few seconds and launches a new browser tab with the project and on the console I can see it logging the contents of the JSON file:

Require ClojureScript's Analyzer API

I'm having trouble requiring ClojureScript's analyzer API:
(ns triangle.core
(:require
[cljs.analyzer.api :as ana-api]))
yields:
clojure.lang.ExceptionInfo : No such namespace: cljs.analyzer.api, could not locate cljs/analyzer/api.cljs, cljs/analyzer/api.cljc, or Closure namespace "cljs.analyzer.api" in file ...
Anybody knows, what could be the reason? Requiring cljs.analyzer works without problems.
Works for me, are you using the latest version?

clj-http/get url {:as :json} doesn't work in script but in REPL

I'm experimenting with Clojure and Leiningen.
I was successful in executing to following line in the REPL:
(print (:body (client/get "https://coinbase.com/api/v1/prices/spot_rate?currency=CAD" {:as :json}))
I created a project with lein new http. When I run the following lines witn lein run then the coercion to JSON doesn't work. It simply prints a correct JSON string.
(ns http.core
(:require [clj-http.client :as client])
(:use clojure.pprint))
(defn -main
[]
(print
(:body
(client/get "https://coinbase.com/api/v1/prices/spot_rate?currency=CAD" {:as :json}))
the output of the script is
{"amount":"306.89","currency":"CAD"}
Any idea what's wrong?
As it turned out there was a breaking change with clj-http version 2.0.0.
Now one has to list explicitly the optional dependencies in project.clj.
After I added
[cheshire "5.5.0"]
to my list of dependencies the program worked as expected.
Please see the documentation for the change here.
I don't know exactly what changed, but [clj-http "1.1.2"] has the behavior you want.

Can I use arbitrary node modules from clojurescript?

Is it possible to use arbitrary node.js modules in a clojurescript project? If yes, how do I go about including them? If not, why not?
Yes, you can, there is nothing special about it:
(def fs (js/require "fs"))
(println (.readdirSync fs js/__dirname))
Be careful with the externs if you don't use optimizations none.
Edit: Does leiningen play with the various js package managers?:
Nope. Since the language does not have packages, it cannot know. You have to do js dependency management and lein deps too. There is a lein-npm and a lein-bower to help with integrating these two package managers.
Yes, since late 2017. With shadow-cljs or Lumo now it's not problem to import npm modules in ClojureScript code anymore.
(ns app.main
(:require ["dayjs" :as dayjs]
["shortid" :as shortid]
["lodash" :as lodash]
["lodash" :refer [isString]]))
Read this topic for details: Guide on how to use/import npm modules/packages in ClojureScript?
Since ClojureScript 1.9.854, there's better support to declare npm modules as dependencies and to require them from your namespaces.
In order to declare it as a dependency, you need to use the :npm-deps compiler option (together with the :install-deps one, if you want lein/bootto automatically install it).
:npm-deps is a map from keyword to string, where the keyword is the name of the dependency you would use to install it using npm and the string is the version of the dependency.
An example of what you could add to your project.clj (if you use lein-cljsbuild), in order to use left-pad:
:cljsbuild {:builds [{:id "prod"
:source-paths ["src"]
:compiler {:main left-pad-demo.core
:output-to "package/index.js"
:target :nodejs
:output-dir "target"
:optimizations :simple
:install-deps true
:npm-deps {:left-pad "1.2.0"}
:pretty-print true}}]})
And then, from your namespace, you can require it like so:
(ns left-pad-demo.core
(:require left-pad))
Or so:
(ns left-pad-demo.core
(:require ["left-pad" :as lp]))
A full working namespace could look like:
(ns left-pad-demo.core
(:require left-pad))
(defn -main [s length]
(console.log (left-pad s length)))
(set! *main-cli-fn* -main)
References:
Requiring Node.js modules from ClojureScript namespaces
ClojureScript is not an Island: Integrating Node Modules